1、NoSQL介绍

(1)什么是NoSQL

NoSQL(NoSQL=Not Only SQL),意思是“不仅仅是SQL”,是一种全新的数据库理念,泛指非关系型的数据库。

我们不能陷入一个误区,就是认为NoSQL既然很强大,是不是就不需要Oracle、MySQL了。其实不是这样的,NoSQL数据库的定位是作为关系数据库的补充,用于应对海量用户和海量数据前提下的数据处理问题,现实应用中通常是两者混合使用。

(2)NoSQL能做什么

1、传统RDBMS和NoSQL

RDBMS基于ER模型实现,数据之间有关联关系,数据和关系都存储在单独的表中。RDBMS支持结构化查询语言(SQL),如数据操纵语言,数据定义语言。RDBMS有严格的数据一致性,支持数据库事务管理。

NoSQL用于非结构化和不可预知的数据,没有提供预定义的模式。NoSQL的存储支持“键-值对”存储、列存储、文档存储、图形数据库等实现方式。NoSQL没有声明性查询语言。NoSQL不支持事务的ACID属性。NoSQL具有高性能、高可用性和可伸缩性。

2、灵活的数据模型

NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。

3、大数据量高性能

NoSQL数据库都具有非常高的读写性能(例如,读可以一秒钟11万,写可以一秒钟8万),尤其在大数据量下,同样表现优秀。这得益于它的无关系型特性,数据库的结构简单。

一般MySQL使用Query Cache,每次更新表时Cache就会失效,它是一种大粒度的Cache。在针对Web2.0的交互频繁的应用中,Cache性能不高,而NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说,就要性能高很多了。

4、易扩展

NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系特性。因为数据之间没有关系,所以就非常容易扩展。无形之间,在架构的层面带来了可扩展的能力。

(3)常见的NoSQL数据库

NoSQL目前比较流行的有三种:Mongodb、Memcache、Redis,其中Mongodb是最像关系型数据库,主要处理文档;单论缓存的处理能力,Memcache是目前最适合的技术;但是需要处理多样化的数据,则Redis更强大。

Redis:几乎覆盖了Memcached的绝大部分功能,数据类型丰富,支持更多的应用场景。数据都在内存中,支持持久化,主要用作备份恢复。

Memcache:很早出现的NoSQL数据库。支持简单的key-value模式,数据都在内存中,一般不持久化。一般是作为高速缓存数据库。

Mongdb:高性能、开源、模式自由(schema free)的文档型数据库,最类似于关系数据库。可以根据数据的特点替代RDBMS成为独立的数据库;或者配合RDBMS来存储特定的数据。

(4)NoSQL应用场景

在电商网站中NoSQL有着大量的应用。

2、NoSQL的四大分类

最初流行的NoSQL数据库是MongoDB,现在使用最广泛的是Redis。

NoSQL数据库有四大分类:键值(Key/Value)、列存储、文档型、图形。

(1)键值(Key/Value)存储数据库

相关产品:Redis、Voldemort、Berkeley DB、Memcache

典型应用:内容缓存,主要用于处理大量数据的高访问负载。

数据模型:键值对

优势:快速查询

劣势:存储的数据缺少结构化

(2)列存储数据库

相关产品:Cassandra、HBase、Riak

典型应用:分布式的文件系统

数据模型:以列簇式存储,将同一列数据存在一起

优势:查找速度快,可扩展性强,更容易进行分布式扩展

劣势:功能相对较少

(3)文档型数据库

相关产品:CouchDB、MongoDB

典型应用:Web应用(与Key-Value类似,Value是结构化的)

数据模型:键值对

优势:数据结构要求不严格

劣势:查询性能不高,而且缺少统一的查询语法

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系型数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

(4)图形数据库

相关产品:Neo4J、InfoGrid、Infinite Graph

典型应用:社交网络,推荐系统等

数据模型:图结构

优势:利用图结构相关算法

劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案

(5)四大分类对比

3、Redis简介

(1)什么是Redis

Redis(Remote Dictionary Server)是C语言开发的一个开源的高性能键值对(Key-Value)数据库。它是完全开源免费的,遵守BSD协议,是当前最热门的NoSQL数据库之一。

Redis特点:

1、使用键值对保存数据,数据间没有必然的关联关系。

2、内部采用单线程机制进行工作。

3、高性能。官方提供测试数据:50个并发执行100000个请求,读的速度是110000次/秒,写的速度是81000次/秒。

4、多种数据类型支持。包括字符串类类型string、列表类型list、散列类型hash、集合类型set、有序集合类型sotrted set。

5、持久化支持,可以进行数据灾难恢复。

(2)Redis的典型应用

1、配合关系型数据库做高速缓存

为热点数据加速查询(主要场景),如热点商品、热点新闻、热点资讯、推广类等高访问量信息等。

分布式数据共享,如分布式集群架构中的session分离。

2、由于拥有持久化能力,利用其多样的数据结构存储特定的数据

任务队列,如秒杀、抢购、购票排列等。

即时信息查询,如各类排行榜、各类网站访问统计、公交到站信息、在线人数信息(聊天室、网站)、设备信号等。

时效性信息控制,如验证码控制、投票控制等。

消息队列

分布式锁

4、使用Docker部署Redis

(1)获取Redis镜像

1、搜索Redis镜像

docker search redis

2、拉取Redis镜像

docker pull redis:6.2.4

3、拉取成功后,可以通过下面命令检查:

docker images | grep redis

(2)运行Redis

1、启动Redis:

docker run -d --name redis -p 6379:6379 redis:6.2.4

注意:一定要带上版本号,否则找不到镜像。

2、通过下面命令检查是否启动成功:

docker ps | grep redis

(3)连接Redis

这里有两种连接Redis的方法:

1、在Pontainer管理界面进入容器redis的Console,使用容器中的客户端程序redis-cli连接到容器中的redis服务器。

2、在Docker宿主机中进入容器里面。

docker exec -it redis /bin/bash

注意:这里-it表示以交互式方式加入到容器中,相当于容器的操作终端。

在容器里面通过Redis命令行工具连接到数据库。也可以不用指定hostname和port,因为默认就是127.0.0.1:6379。

redis-cli -h localhost -p 6379

3、连接成功后就可以操作了:

set site www.climbcloud.com

get site

4、最后可以用exit退出程序。

(4)可视化客户端

RedisDesktopManager是基于Qt 5开发的跨平台工具,支持Windows、Linux和Mac,不过是收费的,还不便宜。

AnotherRedisDesktopManager一款优秀美观的开源免费的可视化工具,支持Windows、Linux和Mac,支持中英文切换,深色白色主题切换。

1、从Github下载和安装AnotherRedisDesktopManager,配置连接如图所示:

2、打开Redis连接,可以看到Redis默认有16个数据库。这里使用默认的DB0。

3、对key进行查询、添加、修改、删除等操作。

在左边会列出当前数据库中所有的key,然后在右边进行查看和修改。

在左边会列出当前数据库中所有的key,然后在右键菜单上可以删除或复制。

在左边使用“New Key”会创建一个新的key,然后在编辑界面中输入value。

4、还提供命令行工具进行交互操作。

二、Redis五大数据类型

1、五大数据类型简介

(1)五种数据类型

根据Redis中存储数据的特点,定义了五种数据类型:

1、字符串类型(String

2、哈希类型(hash):与Map格式类似

3、列表类型(list):LinkedList格式,支持重复元素

4、集合类型(set):不允许重复元素

5、有序集合类型(sorted set):不允许重复元素,且元素有顺序

(2)数据存储结构

Redis自身是一个Map,其中数据都是采用key-value格式存储,其中key都是字符串。数据类型是指value中存储数据的类型,也就是value部分的类型。

2、string基本操作

(1)String类型

字符串类型是最简单的数据存储类型,也是最常用的数据存储类型。在Redis中字符串类型的Value最多可以容纳的数据长度是512M。

字符串类型只能保存单个数据,并且该类型存入和获取的数据相同。

字符串类型在Redis中是二进制安全的,可以接受任何格式的数据,如JPEG图像数据或JSON对象描述等。

字符串的内容如果是整数的形式,可以作为数字操作,但是它的类型还是字符串。

(2)基本操作

1、赋值

set key value:设置key持有指定的字符串value,如果该key存在则进行覆盖操作。总是返回“OK”

2、取值

get key:获取key持有的value。如果与该key关联的value不是String类型,redis将返回错误信息,因为get命令只能用于获取String value。如果该key不存在,返回null。

3、删除

del key:删除该key/value对。

4、添加/修改多个数据

mset key1 value1 key2 value2 ...

5、获取多个数据

mget key1 key2 ...

6、获取数据字符个数(字符串长度)

strlen key

7、追加信息到原始信息后部(如果原始信息存储就追加,否则新建)

append key value

3、数值增减操作

(1)业务场景

大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键id必须保证统一性,不能重复。Oracle数据库具有sequence设置,可以解决该问题,但是MySQL数据库并不具有类似的机制,那么如何解决?

我们可以让redis来控制这些表中主键的值,让它们不重复就可以了。

注意:

1、redis控制数据表主键id,为数据表主键提供生成策略,保障数据表的主键唯一性。

2、此方案适用于所有数据库,且支持数据库集群。

(2)解决方案

设置数值数据增加指定范围的值。

incr key

incrby key increment

incrbyfloat key increment

设置数值数据减少指定范围的值

decr key

decrby key increment

(3)string作为数值操作

string在redis内存存储默认就是一个字符串,当遇到增减类操作incr、decr时会转成数值型进行计算。

redis所有的操作都是原子生的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。

注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis数值上限范围(Java中long最大值9223372036854775807),就会报错。

4、数据时效性设置

(1)业务场景

“最强女生”启动海选投票,只能通过微信投票,每个微信号每4小时只能投1票。

电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门。

新闻网站会出现热点新闻,热点新闻最大的特性是时效性,如何自动控制热点新闻的时效性?

(2)解决方案

设置数据的生命周期。

setex key seconds value

psetex key milliseconds value

注意:执行setex后开始计时,但是这时又使用set重置了数据的值,则setex会失效。

(3)小结

redis控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作。

5、key的命名规范

(1)业务场景

主页高频访问信息显示控制,例如新浪微博大V主页显示粉丝数与微博数量。这些数据是热门数据,redis可以应用于各种结构型和非结构型高热度数据访问加速,但是在redis中如何存储呢?

(2)解决方案

1、在redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可。

这里key的命名是“表名+主键名+值+属性名”,中间使用冒号隔开。

2、在redis中以json格式存储大V用户信息,定时刷新(也可以使用hash类型)。

实际应用中,上面两种存储方式都可以,但是第一种方式可以直接修改值,比较方便。第二种方式先要取出整个JSON数据,修改后再将整个JSON数据存入。

(3)key的设置约定

数据库中的热点数据key命名惯例:

6、hash类型介绍与基本操作

(1)数据存储的问题

前面讲解了使用JSON格式来存储数据,但是对象类型数据的存储如果有较频繁的更新需求操作会显得笨重。

那么分别存储各个数据呢?这也有问题,因为这些数据毕竟还是属于一个对象。

我们看到上面数据的右边就是对象的属性和值,将上面结构简化为下面的结构,对象本身作为key,而对象的属性和值的集合作为键值对集合,这就是hash数据类型。

(2)hash类型

Hash类型对一系列存储的数据进行编组,从而方便管理,典型应用就是存储对象信息。Hash类型是一个键值对集合,类似Java里面的Map<String,Object>。

hash类型根据存储数据的不同,会对存储结构进行优化。如果field数量较少,存储结构会优化为类似数组的结构。如果field数量较多,存储结构使用HashMap结构。

(3)基本操作

1、赋值

hset key field value:为指定的key设定field/value对(键值对)。

hmset key filed value [field2 value2 ...]:设置key中的多个filed/value

2、取值

hget key field:返回指定的key中的field的值

hmget key fileds:获取key中的多个filed的值

hgetall key:获取key中的所有filed-vaule

3、删除

hdel key field [field ...]:可以删除一个或多个字段,返回值是被删除的字段个数

del key:删除整个list

4、添加/修改多个数据

hmset key field1 value1 field2 value2 ...

5、获取多个数据

hmget key field1 field2 ...

6、获取哈希表中字段的数量

hlen key

7、获取哈希表中是否存在指定的字段

hexists key field

7、list类型介绍与基本操作

(1)list类型介绍

List类型保存多个数据,底层使用双向链接存储结构。List类型是按照写入顺序排序的链表。我们可以在其头部(left)和尾部(right)添加新的元素。如果插入时该键不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。

(2)基本操作

1、两端添加/修改数据

lpush key value1 value2...:在指定的key所关联的list的头部插入所有的values,如果该key不存在,该命令在插入的之前创建一个与该key关联的空链表,之后再向该链表的头部插入数据。插入成功则返回元素的个数。

rpush key value1、value2…:在该list的尾部添加元素。

2、获取数据列表

lrange key start end:获取链表中从start到end的元素的值,start、end可为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推…

lindex key index

3、两端获取并删除数据

lpop key:返回并弹出指定的key关联的链表中的第一个元素,即头部元素。

rpop key:从尾部弹出元素。

4、获取列表中元素的个数

llen key:返回指定的key关联的链表中的元素的数量。

8、set类型介绍与基本操作

(1)set类型介绍

Set类型可以看作为没有排序的List类型,但是Set集合中不允许出现重复的元素。

set类型与hash存储结构完全相同,仅存储key,不存储值(nil),并且key是不允许重复的。由于hash结构的查询速度快,因此set类型在查询方面提供更高的效率。

(2)基本操作

1、添加元素

sadd key value1 value2…:向set中添加数据,如果该key的值已有则不会重复添加。

2、获得集合中的元素

smembers key:获取set中所有的成员。

sismember key member:判断参数中指定的成员是否在该set中,1表示存在,0表示不存在或者该key本身就不存在。

3、删除元素

srem key member1、member2…:删除set中指定的成员

4、获取集合数据总量

scard key

5、判断集合中是否包含指定数据

sismember key member

9、sortedset类型介绍与基本操作

(1)sortedset类型介绍

SortedSet(zset)和Set类型极为相似,都是字符串的集合,都不允许出现重复的成员。

SortedSet是一种可以根据自身特征进行排序的集合,在set的存储结构基础上添加了可排序字段。每个集合成员都关联一个double类型的分数(score),Redis正是通过分数来为集合中的成员进行从小到大的排序。注意:SortedSet成员是唯一的,但分数(score)可以重复。

(2)基本操作

1、添加数据

zadd key score member score2 member2 … :将所有成员以及该成员的分数存放到sorted-set中。

2、获得全部数据

zrange key start end [withscores]:获取索引从start到end之间的所有元素(包括两端的元素),[withscores]参数表明返回的成员包含其分数。

zrevrange key start stop [WITHSCORES]

3、删除数据

zrem key member[member…]:移除集合中指定的成员,可以指定多个成员。

三、持久化

1、持久化简介

(1)数据恢复的问题

我们用word编辑文档时,可能会出现程序意外崩溃。Word提供了一个自动恢复功能,可以自动保存我们编辑的文档,在意外发生之后,通过这个功能可以恢复之前的编辑内容。

这种自动备份功能的原理实际很简单,就是将内存中的数据自动保存到硬盘中,以后可以将硬盘中的数据恢复到内存中。

(2)什么是持久化

这种利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化。持久化的目的,就是防止数据的意外丢失,确保数据安全性。

Redis用于保存程序中的数据,它也提供了类似的自动备份的功能,也就是持久化功能。

(3)持久化过程保存什么

Redis的高性能是由于其将所有数据都存储在内存中,当Redis服务器重启后,数据会丢失。为了使Redis在重启之后仍能找回数据,需要将数据从内存中持久化到硬盘文件中。

通常有两种持久化机制:

1、以快照形式将当前数据状态进行保存。这种存储格式简单,关注点在数据本身。这种方式的问题是程序自动定时保存,但是程序崩溃时,最后编辑的内容可能会丢失。

2、以日志形式将数据的操作过程进行保存,相当于Word中的Undo功能。这种存储格式复杂,并且关注点在数据的操作过程。

Redis支持上面两种持久化方式,快照方式称为RDB,操作方式称为AOF。其中RDB机制是Redis的默认持久化方式。

2、持久化RDB

(1)save指令演示

我们每次执行一次持久化指令save,就会以RDB方式执行一次保存操作。

1、数据库中原先没有数据,我们写入一条数据,然后使用save指令保存。

2、Redis默认把持久化文件保存在data目录中。

打开data目录后,可以看到快照文件dump.rdb。这个文件内容是二进制格式保存的,因此打开后内容是看不懂的。

(2)RDB相关配置

在redis配置文件中有一些与save指令相关的配置项。

(3)数据恢复演示

前面保存了数据,那么这些数据是否能恢复呢?

1、在Portainer管理界面中先使用kill杀掉redis服务,这就模拟了服务器的崩溃。然后再次启动redis服务。

2、使用客户端查看redis服务,可以看到前面保存的key/value,表示在redis服务启动时,数据被成功的从快照文件中恢复了。

3、持久化AOF简介

(1)RDB存储的弊端

1、存储数据量较大,效率较低。RDB是基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低。

2、大数据量下的IO性能较低,导致Redis服务响应缓慢。

3、基于fork创建子进程来持久化,会产生额外的内存消耗。

4、快照是某个时间点的内存数据,意外宕机可能带来部分数据丢失的风险。

(2)解决思路

1、不用记录所有数据,仅仅记录部分数据。

2、不再记录内存中所有数据,而是记录整个操作过程。

3、对所有操作均进行记录,排除了丢失数据的风险。

(3)AOF概念

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令,就可以达到恢复数据的目的。与RDB相比可以简单描述为改记录数据为记录数据产生的过程。

AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。实际应用中优先使用AOF持久化。

4、AOF持久化策略

(1)AOF持久化策略

客户端发送到Redis服务的指令,Redis服务并没有马上持久化,而是把指令放在一个AOF写命令刷新缓冲区中,最终Redis服务会将缓冲区中的指令写入到AOF文件中。

现在就有一些问题:

一次向AOF文件中写入多少条记录?

多长时间写一次呢?

为了回答这些问题,Redis服务提供了AOF写数据的三种策略(appendfsync):

1、always(每次)

每次写入操作均同步到AOF文件中,数据零误差,性能较低。这种方式不太好,特别是每秒有几十万条指令请求时,Redis服务的性能会非常差。

2、everysec(每秒)

每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高,但是在系统突然宕机的情况下会丢失最后1秒内的数据。

这种方式降低了写AOF文件的频率,提高了IO性能。

3、no(系统控制)

由操作系统控制每次同步到AOF文件的周期,整体过程不可控。

(2)AOF配置

AOF有几个相关配置:

1、appednonly表示是否开启AOF持久化功能,默认为不开启状态。

AOF功能开启的配置:appednonly yes|no

2、AOF写数据策略的配置:appendfsync always|everysec|no

3、AOF持久化文件名:appendfilename filename

默认文件名为appendonly.aof,建议配置为appendonly-端口号.aof。

4、dir是AOF持久化文件的保存路径,与RDB持久化文件保持一致即可。

(3)配置Redis容器

1、在宿主机中创建本地目录:

mkdir /data/redis/data -p

其中redis目录存放配置文件,data子目录用于持久化存储和Log输出。

2、在redis目录中创建配置文件redis.conf,其中配置文件内容为:

### NETWORK ###

port 6379

#### GENERAL ###

daemonize no             # Docker容器不能开启守护进程,否则容器无法跑起来

logfile "/data/redis.log"     # Redis默认不记录日志,需要自己配置

### SECURITY ###

# requirepass foobared     #配置redis访问密码

### APPEND ONLY MODE ####

appendonly yes            # 开启AOF,默认保存在/data目录中

appendfilename "appendonly.aof"

appendfsync always # 每一次操作都进行持久化

# appendfsync everysec # 每隔一秒进行一次持久化

# appendfsync no:# 不进行持久化

3、为docker访问宿主机目录授权:

cd /data

chmod 777 -R redis

(4)启动Redis容器

1、创建redis容器

docker create \

--name myredis \

-p 6379:6379 \

-v /data/redis/redis.conf:/usr/local/etc/redis/redis.conf \   外部配置

-v /data/redis/data:/data \                            持久存储

-e "TZ=Asia/Shanghai" \

redis:6.2.4 redis-server \                                       带存储启动

/usr/local/etc/redis/redis.conf                             使用外部配置启动

注意:redis镜像中默认存储是/data卷,而redis-server支持带存储启动

2、启动redis容器

docker start myredis

3、Redis服务会自动生成一个/redis/data/appendonly.aof文件,这就是AOF文件。

(5)数据恢复演示

1、在Redis客户端中写入一个数据。

2、在Portainer管理界面中先使用kill杀掉redis服务,这就模拟了服务器的崩溃。然后再次启动redis服务。

2、使用客户端查看redis服务,可以看到前面保存的key/value,表示在redis服务启动时,数据被成功的从快照文件中恢复了。

5、持久化_RDB与AOF方案比对

1、优点

RDB速度慢,相比较AOF需要对每次操作进行记录,RDB的数据备份速度要慢许多。

因为AOF是忠实记录每次操作,所以AOF会完整的记录每个数据,保证数据的完整性。

2、缺点

RDB备份机制(比如每3分钟进行10次操作)可能会遗漏最后一段的数据。

四、SpringBoot整合Redis

1、Lettuce

(1)Lettuce简介

Redis有三个常用的连接客户端:Jedis、Redisson、Lettuce。在SpringBoot2之后,Redis的默认连接是lettuce。

Lettuce是一个可伸缩线程安全的Redis客户端。Lettuce的连接是基于Netty的,利用NIO框架netty来高效地管理连接,并且一个连接实例可以在多个线程间共享。

(2)添加redis的起步依赖

<!-- 配置使用redis启动器 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<!-- lettuce pool连接池 -->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-pool2</artifactId>

</dependency>

(3)测试Lettuce

public class RedisTest {

public static void main(String[] args) {

// 连接Redis

RedisClient redisClient = RedisClient.create("redis://192.168.4.202/0");

StatefulRedisConnection<String, String> connection = redisClient.connect();

System.out.println("Connected to Redis");

// 获取Redis命令对象

RedisCommands<String, String> redisCommands = connection.sync();

// 设值和取值

redisCommands.set("key", "Hello World");

String value = redisCommands.get("key");

System.out.println(value);

// 关闭连接

connection.close();

redisClient.shutdown();

}

}

2、StringRedisTemplate

(1)配置redis连接

在application.yml中配置redis的连接信息。

spring:

redis:

host: 192.168.4.202

port: 6379

database: 0             # Redis数据库索引(默认为0)

lettuce:

pool:

max-wait: 100000     # 连接池最大阻塞等待时间(负数表示无限制)

max-active: 100      # 连接池最大连接数(负数表示无限制)

max-idle: 10         # 连接池中的最大空闲连接

min-idle: 0          # 连接池中的最小空闲连接

timeout: 5000            # 连接超时

(2)StringRedisTemplate简介

spring-boot-starter-data-redis会自动在Spring容器中注入StringRedisTemplate和RedisTemplate对象。StringRedisTemplate继承了RedisTemplate,两者对Redis的操作相似。

StringTemplate类中方法存取的key-value值是String类型。

StringRedisTemplate.opsForValue().*        //操作String字符串类型

StringRedisTemplate.delete(key/collection)   //根据key/keys删除

StringRedisTemplate.opsForList().*          //操作List类型

StringRedisTemplate.opsForHash().*         //操作Hash类型

StringRedisTemplate.opsForSet().*          //操作set类型

StringRedisTemplate.opsForZSet().*         //操作有序set

(3)常见操作

@SpringBootTest

public class StringRedisTemplateTest {

@Autowired

private StringRedisTemplate stringRedisTemplate;

@Test

public void testString() throws InterruptedException {

ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();

// 设值和取值

valueOperations.set("demo", "Hello Redis");

System.out.println(valueOperations.get("demo"));

// 数字操作

valueOperations.set("count", "1");

valueOperations.increment("count"); // +1

valueOperations.increment("count", 2); // +2

System.out.println(valueOperations.get("count"));

valueOperations.decrement("count");

System.out.println(valueOperations.get("count"));

// 数据时效性

valueOperations.set("tel", "1", 10, TimeUnit.SECONDS);

System.out.println(valueOperations.get("tel"));

Thread.sleep(12*1000);

System.out.println(valueOperations.get("tel"));

// key命名规范

Long id = 1L;

String fans_key = "user:id:" + id + "fans";

valueOperations.set(fans_key, "122");

System.out.println(valueOperations.get(fans_key));

String user_key = "user:id" + id;

String json = "{id:1, name:张三, fans:122, blogs:6}";

valueOperations.set(user_key, json);

System.out.println(valueOperations.get(user_key));

}

@Test

public void testHash() {

HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();

// 设值

hashOperations.put("myhash", "id", "1");

Map<String, String> objectMap = new HashMap<>();

objectMap.put("name", "张三");

objectMap.put("fans", "122");

objectMap.put("blogs", "6");

hashOperations.putAll("myhash", objectMap);

// 取值

System.out.println(hashOperations.get("myhash", "fans"));

// 取多个值

List<String> keys = new ArrayList<>();

Collections.addAll(keys,"id", "name", "fans", "blogs");

System.out.println(hashOperations.multiGet("myhash", keys));

// 删除

stringRedisTemplate.delete("myhash");

System.out.println(hashOperations.get("myhash", "fans"));

}

@Test

public void testList() {

ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();

// 设值

listOperations.leftPushAll("mylist", "a", "b", "c", "d", "e");

// 取值

System.out.println(listOperations.range("mylist", 0, -1));

// 两端取值

System.out.println(listOperations.leftPop("mylist"));

System.out.println(listOperations.rightPop("mylist"));

// 删除

stringRedisTemplate.delete("mylist");

}

@Test

public void testSet() {

SetOperations<String, String> setOperations = stringRedisTemplate.opsForSet();

// 设值

setOperations.add("myset", "a", "b", "c", "d", "e");

// 获得所有成员

System.out.println(setOperations.members("myset"));

// 判断集合中是否有指定成员

System.out.println(setOperations.isMember("myset", "d"));

// 删除

stringRedisTemplate.delete("myset");

}

@Test

public void testSortedSet() {

ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();

// 设值

zSetOperations.add("mysort", "a", 100d);

// 设置多个值

Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<ZSetOperations.TypedTuple<String>>();

tuples.add(new DefaultTypedTuple<String>("b", 50d));

tuples.add(new DefaultTypedTuple<String>("c", 60d));

tuples.add(new DefaultTypedTuple<String>("d", 40d));

tuples.add(new DefaultTypedTuple<String>("e", 80d));

zSetOperations.add("mysort",tuples);

// 获得所有成员

System.out.println(zSetOperations.range("mysort", 0, -1));

// 获得所有成员,并且返回分数

System.out.println(zSetOperations.rangeWithScores("mysort", 0, -1));

// 删除

stringRedisTemplate.delete("mysort");

}

}

3、序列化接口

(1)RedisTemplate保存数据

1、运行下面的测试代码,数据被写入到Redis中,并且成功返回了结果。

@SpringBootTest

public class RedisTemplateTest {

@Autowired

private RedisTemplate redisTemplate;

@Test

public void testString() throws InterruptedException {

ValueOperations valueOperations = redisTemplate.opsForValue();

// 设值和取值

valueOperations.set("demo", "Hello Redis");

System.out.println(valueOperations.get("demo"));

}

}

2、运行Redis客户端,发现存进去的key和value前面都会自动加上一串看不懂的东西,但是前面使用StringRedisTemplate执行相同的操作则没有问题。

(2)序列化接口

RedisTemplate是StringTemplate的父类。StringRedisTemplate中存取的key-value值是String类型,RedisTemplate中存取的key-value值是Object类型。

我们看到RedisTemplate中有4个序列化相关的属性,主要用于KEY和VALUE的序列化,也就是说,将key和value的序列化结果保存到Redis中。例如,通常会将POJO对象使用JSON方式序列化成字符串,然后存储到Redis中。

RedisSerializer接口是Redis序列化接口,用于Redis KEY和VALUE的序列化。

(3)序列化问题的分析

StringTemplate和RedisTemplate之间的主要区别是序列化类。

StringRedisTemplate默认使用字符串序列化方式,即StringRedisSerializer。

RedisTemplate默认使用JDK序列化方式,即JdkSerializationRedisSerializer。KEY和Value前面带着奇怪的16进制字符,就是ObjectOutputStream#writeString(String str, boolean unshared)输出的“标志位 + 字符串长度”。

(4)序列化的选择

当redis数据库中存取的数据是字符串类型的时候,那么使用StringRedisTemplate即可。

如果数据是复杂的对象类型,在取出时又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。

4、RedisTemplate

我们希望使用Redis缓存Java对象,在存放和获取Java对象时不需要做任何的数据转换,直接从Redis取出来,也就是说,在RedisTemplate中隐藏Java对象和JSON数据之间的相互转换过程。

(1)加入Jackson依赖

这里使用Jackson序列化JSON数据,也需要引入。

<!-- jackson序列化 -->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

</dependency>

(2)配置JSON序列化

下面来使用Jackson来设置RedisTemplate的序列化。

@Configuration

@ConditionalOnClass(RedisOperations.class)

public class RedisConfig {

// 配置RedisTemplate

@Bean

public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

redisTemplate.setConnectionFactory(redisConnectionFactory);

// key使用字符串,序列化用 StringRedisSerializer

redisTemplate.setKeySerializer(new StringRedisSerializer());

// 使用Jackson2JsonRedisSerialize 替换默认的 jdkSerializeable 序列化

ObjectMapper objectMapper = new ObjectMapper();

objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),

ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

// 解决jackson2无法反序列化LocalDateTime的问题

JavaTimeModule javaTimeModule = new JavaTimeModule();

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dtf));

javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dtf));

objectMapper.registerModule(javaTimeModule);

// 设置value的序列化规则

GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

redisTemplate.afterPropertiesSet();

return redisTemplate;

}

}

(3)测试redis操作

@SpringBootTest

public class RedisTemplateTest {

@Autowired

private RedisTemplate redisTemplate;

@Test

public void testString() throws InterruptedException {

ValueOperations valueOperations = redisTemplate.opsForValue();

// 设值和取值

valueOperations.set("demo", "Hello Redis");

System.out.println(valueOperations.get("demo"));

}

@Test

public void testObject() {

Teacher teacher = new Teacher();

teacher.setId(1L);

teacher.setName("张三");

teacher.setGmtCreate(LocalDateTime.now());

ValueOperations<String, Teacher> valueOperations = redisTemplate.opsForValue();

valueOperations.set("teacher:id:1", teacher);

Teacher result = valueOperations.get("teacher:id:1");

System.out.println(result);

System.out.println(result.getGmtCreate());

}

@Test

public void testList() {

Teacher teacher1 = new Teacher();

teacher1.setId(1L);

teacher1.setName("张三");

teacher1.setGmtCreate(LocalDateTime.now());

Teacher teacher2 = new Teacher();

teacher2.setId(2L);

teacher2.setName("李四");

teacher2.setGmtCreate(LocalDateTime.now());

List<Teacher> teacherList = new ArrayList<>();

teacherList.add(teacher1);

teacherList.add(teacher2);

ValueOperations<String, List<Teacher>> valueOperations = redisTemplate.opsForValue();

valueOperations.set("teachers", teacherList);

List<Teacher> result = valueOperations.get("teachers");

for(Teacher t : result) {

System.out.println(t);

System.out.println(t.getGmtCreate());

}

}

}

5、在线教育项目中的Redis缓存

ClimbUserDetailsServiceImpl中根据username查询User、Role集合、Permission集合。因为这些查询操作比较频繁,所以将它们缓存在Redis中。

实现思路:在获取数据库之前,先在缓存中查找数据。如果缓存中有数据则不再查询数据库;如果缓存中没有数据则查询数据库,并将查询结果保存到Redis中。

@Slf4j

@Service

public class ClimbUserDetailsServiceImpl implements UserDetailsService {

@Autowired

private UserMapper userMapper;

// Redis

@Autowired

private RedisTemplate redisTemplate;

/*

支持缓存的版本

1. 用户:user:{username}

2. 角色:role:{username}

3. 权限:perm:{username}

*/

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

// 1. 从缓存中读取用户数据

ValueOperations<String, User> userOperations = redisTemplate.opsForValue();

User user = userOperations.get("user:" + username);

if(user == null) {

// 从数据源中读取用户信息

QueryWrapper<User> queryWrapper = new QueryWrapper<>();

queryWrapper.eq("username", username);

user = userMapper.selectOne(queryWrapper);

// 写入Redis缓存

userOperations.set("user:" + username, user);

}

if (user == null) {

throw new UsernameNotFoundException(String.format("用户['%s']不存在", username));

}

// 2. 从缓存中读取角色数据

ValueOperations<String, List<Role>> roleOperations = redisTemplate.opsForValue();

List<Role> roleList = roleOperations.get("role:" + username);

if(roleList == null) {

// 读取用户的所有角色

roleList = userMapper.selectRoleForUsername(user.getId());

// 写入Redis缓存

roleOperations.set("role:" + username, roleList);

}

// 3. 从缓存中读取权限数据

ValueOperations<String, List<Permission>> permOperations = redisTemplate.opsForValue();

List<Permission> permissionList = permOperations.get("perm:" + username);

if(permissionList == null) {

// 读取用户的所有权限

permissionList = userMapper.selectPermissionForUsername(user.getId());

// 写入Redis缓存

permOperations.set("perm:" + username, permissionList);

}

// 4. 构造security用户

return new SecurityUser(user, roleList, permissionList);

}

}

Redis基础(含代码)相关推荐

  1. Reportlab基础教程03之如何绘制线圆椭圆扇形正方形(含代码)

    绘制线 # drawing_lines.pyfrom reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas ...

  2. 前端实现红包雨功能_最全解密微信红包随机算法(含代码实现)

    code小生 一个专注大前端领域的技术平台公众号回复 Android加入安卓技术群 "  1.引言 这个系列文章已经整理了10篇,但都没有涉及到具体的红包算法实现,主要有以下两方面原因.一方 ...

  3. 最全解密微信红包随机算法(含代码实现)

    code小生 一个专注大前端领域的技术平台 公众号回复Android加入安卓技术群 " 本文内容编写时,参考了网上的资料,详见"参考资料"部分,感谢分享者..本文已同步发 ...

  4. 用python描述车_使用Python探索二手车市场(含代码)

    原标题:使用Python探索二手车市场(含代码) 感谢关注天善智能,走好数据之路↑↑↑ 欢迎关注天善智能,我们是专注于商业智能BI,人工智能AI,大数据分析与挖掘领域的垂直社区,学习,问答.求职一站式 ...

  5. 动图图解C语言插入排序算法,含代码分析

    C语言文章更新目录 C语言学习资源汇总,史上最全面总结,没有之一 C/C++学习资源(百度云盘链接) 计算机二级资料(过级专用) C语言学习路线(从入门到实战) 编写C语言程序的7个步骤和编程机制 C ...

  6. 动图图解C语言选择排序算法,含代码分析

    C语言文章更新目录 C语言学习资源汇总,史上最全面总结,没有之一 C/C++学习资源(百度云盘链接) 计算机二级资料(过级专用) C语言学习路线(从入门到实战) 编写C语言程序的7个步骤和编程机制 C ...

  7. python多分类混淆矩阵代码_深度学习自学记录(3)——两种多分类混淆矩阵的Python实现(含代码)...

    深度学习自学记录(3)--两种多分类混淆矩阵的Python实现(含代码),矩阵,样本,模型,类别,真实 深度学习自学记录(3)--两种多分类混淆矩阵的Python实现(含代码) 深度学习自学记录(3) ...

  8. Redis基础-下载安装配置

    Nosql: NoSQL:即 Not-Only SQL( 泛指非关系型的数据库),作为关系型数据库的补充. 作用: 应对基于海量用户和海量数据前提下的数据处理问题. 特征: 可扩容,可伸缩 大数据量下 ...

  9. redis 基础数据类型及应用 1

    redis 基础数据类型及应用 1 redis简介 一. string 数据类型与结构(字符串) 1 string类型简介 2 常用命令 1.set 命令 2.get 命令 3.getset 命令 4 ...

  10. Redis 基础 - 优惠券秒杀《非集群》

    参考 Redis基础 - 基本类型及常用命令 Redis基础 - Java客户端 Redis 基础 - 短信验证码登录 Redis 基础 - 用Redis查询商户信息 摘要 用Redis生成保证唯一性 ...

最新文章

  1. 信息熵及其相关概念--数学
  2. Linux命令详解:[7]获得命令帮助
  3. Manage Jenkins管理界面提示“依赖错误: 部分插件由于缺少依赖无法加载...“问题解决办法
  4. 梯度下降法原理及实现
  5. jsDate对象和倒计时图片案例
  6. python 扫盲系列(1)
  7. SIFT算法原理(不带公式)
  8. 使用IE WebControls中的TabStrip控件和MultiPage控件实现选项卡式风格页面(转载)
  9. Elasticsearch 实战经验总结
  10. 【ArcGIS风暴】ArcGIS 10.2导入Excel数据X、Y坐标(经纬度、平面坐标),生成Shapefile点数据图层
  11. Linux环境进程间通信(五): 共享内存(上)
  12. PHP 将Base64图片保存到 Sae storage
  13. 打通JAVA与内核系列之一ReentrantLock锁的实现原理
  14. 这个顶级AI赛事总奖池100W+!CV 、NLP赛题等你来战!
  15. window2008 64位系统无法调用Microsoft.Office.Interop组件进行文件另存的解决办法
  16. linux 脚本自动添加防火墙规则
  17. .AsEnumerable() 和 .ToList() 的区别:
  18. 代码整洁之道读书笔记----第四章---注释--第一节-什么是好注释如何写好注释
  19. Unity 下载安装Standard Assets
  20. 色彩设计原理(里面有配色方案,也有配色网站)

热门文章

  1. c语言函数设计星号,《C语言及程序设计》实践参考——函数版星号图
  2. html5carousel图片轮播,jquery 3d Carousel轮播图插件
  3. c#学习笔记05-treeview中添加图标
  4. 关于重装系统时读取不到硬盘和设置主板AHCI蓝屏问题
  5. vue项目安装使用 Ant Design 和 sass
  6. 二维码在语文教学可以怎么应用
  7. 电脑出问题解决办法(Win8)
  8. Ubuntu中opencv图片上输出文字
  9. 【python】通过loging模块将日志写入mysql数据库
  10. Scrapy 爬取网易云音乐播放量百万以上的歌单以及歌单详情