一文读懂非关系型数据库Redis

Redis介绍

3.1 什么是NOSQL

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

3.2.为什么需要学习NOSQL

​ 随着互联网的高速崛起,网站的用户群的增加,访问量的上升,传统(关系型)数据库上都开始出现了性能瓶颈,web程序不再仅仅专注在功能上,同时也在追求性能。所以NOSQL数据库应运而上,具体表现为对如下三高问题的解决:

  • High performance - 对数据库高并发读写的需求

    ​ web2.0网站要根据用户个性化信息来实时生成动态页面和提供动态信息,所以基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到每秒上万次读写请求。关系数据库应付上万次SQL查询还勉强顶得住,但是应付上万次SQL写数据请求,硬盘IO就已经无法承受了。其实对于普通的BBS网站,往往也存在对高并发写请求的需求,例如网站的实时统计在线用户状态,记录热门帖子的点击次数,投票计数等,因此这是一个相当普遍的需求。

  • Huge Storage - 对海量数据的高效率存储和访问的需求

    ​ 类似Facebook,twitter,Friendfeed这样的SNS网站,每天用户产生海量的用户动态,以Friendfeed为例,一个月就达到了2.5亿条用户动态,对于关系数据库来说,在一张2.5亿条记录的表里面进行SQL查询,效率是极其低下乃至不可忍受的。再例如大型web网站的用户登录系统,例如腾讯,盛大,动辄数以亿计的帐号,关系数据库也很难应付。

  • High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求

    ​ 在基于web的架构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,你的数据库却没有办法像web server和app server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展是非常痛苦的事情,往往需要停机维护和数据迁移,为什么数据库不能通过不断的添加服务器节点来实现扩展呢?

3.3 主流的NOSQL产品

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aW4vaip2-1688052369979)(img/1.png)]

  • 键值(Key-Value)存储数据库

  • 列存储数据库(分布式)

  • 文档型数据库 (Web应用与Key-Value类似,Value是结构化的)

  • 图形(Graph)数据库(图结构)

3.4 NOSQL的特点

​ 在大数据存取上具备关系型数据库无法比拟的性能优势,例如:

  • 易扩展

    ​ NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。

  • 大数据量,高性能

        NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单、在内存中存取数据。
    
  • 灵活的数据模型

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

  • 高可用

    ​ NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如Cassandra,HBase模型,通过复制模型也能实现高可用。

4.小结

  1. NoSql: 非关系型数据库

  2. 为什么要学习NoSQL

    • 高并发读写
    • 海量数据查询效率
    • 高扩展, 高可用

    解决关系型数据库的瓶颈

  3. Nosql产品

    • Redis
    • MongoDB

知识点-Redis概述

1.目标

  • 知道什么是Redis以及Redis的应用场景

2.路径

  • 什么是Redis
  • redis的应用场景

3.讲解

3.1.什么是Redis

​ Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库数据是保存在内存里面的. 官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:

  • 字符串类型 string(最常用)
  • 散列类型 hash
  • 列表类型 list
  • 集合类型 set
  • 有序集合类型 sortedset
3.2 redis的应用场景
  • 缓存(数据查询、短连接、新闻内容、商品内容等等)
  • 任务队列。(秒杀、抢购、12306等等)
  • 数据过期处理(可以精确到毫秒, 短信验证码)
  • 分布式集群架构中的session分离 session 服务器里面
  • 聊天室的在线好友列表
  • 应用排行榜
  • 网站访问统计

4.小结

  1. Redis: 由C语言编写的一种NoSQL, 以key-value存在, 数据保存在内存里面 性能特别高
  2. Redis应用场景
    • 缓存(eg: 电商项目里面首页的轮播图)
    • 队列(eg: 秒杀)
    • 数据过期处理(eg: 短信验证码…)
    • 分布式集群架构中的session分离

实操-window版Redis的安装

1.目标

  • 掌握window版Redis的安装与使用

2.路径

  1. Redis的下载

  2. Redis的安装

  3. 启动

3.讲解

3.1.windows版Redis的下载

​ 官方提倡使用Linux版的Redis,所以官网值提供了Linux版的Redis下载,我们可以从GitHub上下载window版的Redis,具体链接地址如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HuiWdwE5-1688052369980)(img/8.png)]

在今天的课程资料中提供的下载完毕的window版本的Redis:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K26JumIT-1688052369981)(img/2.png)]

3.2 Redis的安装

解压Redis压缩包后,见到如下目录机构:

目录或文件 作用
redis-benchmark 性能测试工具
redis-check-aof AOF文件修复工具
redis-check-dump RDB文件检查工具(快照持久化文件)
redis-cli 命令行客户端
redis-server redis服务器启动命令
redis.windows.conf redis核心配置文件
3.3 启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quyE7sfC-1688052369982)(img/tu_10.png)]

  • 安装:window版的安装极其简单,解压Redis压缩包完成即安装完毕
  • 启动与关闭: 双击Redis目录中redis-server.exe可以启动redis服务,Redis服务占用的端口是6379

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZFi3755b-1688052369983)([外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ck66fmgB-1688052370654)(img/3.png)]]

  • 点击redis-cli

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2m8jqhor-1688052369983)(img/4.png)])

4.小结

  1. redis目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXHkYCSB-1688052369984)(img/1560064246957.png)]

  1. 启动

    • 先点击redis-server.exe
  • 再点击redis-cli.exe
  1. Redis

    • 端口是 6379
    • 默认不需要密码

实操-Linux版本Redis的安装

1.目标

  • 掌握Redis的安装

2.讲解

  1. 在虚拟机中安装c++环境
yum -y install gcc-c++
  1. 下载Redis
  2. 上传到Linux
  3. 解压
tar -zxf redis-4.0.14.tar.gz
  1. 编译
cd redis-4.0.14
make
  1. 创建一个目录
mkdir /usr/local/redis
  1. 安装
make install PREFIX=/usr/local/redis
  1. 进入安装好的redis目录,复制配置文件
cd /usr/local/redis/bin
cp /root/redis-4.0.14/redis.conf  /usr/local/redis/bin      -- 这句命令的作用是将redis-4.0.14中的redis.conf文件拷贝到bin目录
  1. 修改配置文件
# 修改配置文件
vi redis.conf
# Redis后台启动
修改 daemonize 为 yes
# Redis服务器可以跨网络访问 
修改 bind0.0.0.0
# 开启aof持久化,这个可以不做
appendonly yes
  1. 启动redis
./redis-server redis.conf
  1. 如果想远程连接redis,那么就要放行6379端口

3.小结

实操-Redis的客户端安装

1.目标

​ 装Redis的客户端

2.步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cniG24mq-1688052369985)(img/image-20191223100124678.png)]

  • 双击, 下一步 …
  • 安装没有中文和空格目录

第二章-Redis的数据类型

知识点-redis中数据结构/类型

1.目标

  • 能够说出Redis中的数据类型

2.路径

  • Redis的数据类型类型介绍
  • Redis中的key

3.讲解

3.1Redis的数据类型【面试】

​ redis中存储的数据是以key-value的形式存在的.其中value支持5种数据类型 .在日常开发中主要使用比较多的有字符串、哈希、字符串列表、字符串集合四种类型,其中最为常用的是字符串类型。

​ 字符串(String)

​ 哈希(hash) 类似HashMap

​ 字符串列表(list)

​ 字符串集合(set) 类似HashSet

​ 有序的字符串集合(sorted-set或者叫zset)

3.2 key
  • key不要太长(不能>1024个字节)

  • 也不要太短 . 可读性差.

    项目名_模块_key  eg: mm_user_name
    

4.小结

  1. Redis数据类型
  • 字符串
  • Hash
  • List
  • Set
  • ZSET

知识点-Redis字符串(String)

1.目标

  • 能够使用redis的string操作命令

2.路径

  1. Redis字符串(String)概述
  2. 应用场景
  3. 常见命令
  4. 应用举例

2.讲解

2.1概述

​ string是redis最基本的类型,用的也是最多的,一个key对应一个value。 一个键最大能存储512MB.

2.2 应用场景
  1. 缓存功能:字符串最经典的使用场景,redis最为缓存层,Mysql作为储存层,绝大部分请求数据都是在redis中操作,由于redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用。

  2. 计数器功能:比如视频播放次数,点赞次数。

  3. ID递增

2.3常见命令
命令 描述
SET key value(重点) 设置指定 key 的值
GET key(重点) 获取指定 key 的值
DEL key 删除key
GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
SETNX key value 只有在 key 不存在时设置 key 的值。
INCR key(重点) 将 key 中储存的数字值增一。
INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。
DECR key 将 key 中储存的数字值减一。
DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。
2.4应用举例

商品编号、订单号采用string的递增数字特性生成。

定义商品编号key:items:id
192.168.101.3:7003> INCR items:id
(integer) 2
192.168.101.3:7003> INCR items:id
(integer) 3

4.小结

4.1String
  1. String是用的最多的一个数据类型.
  2. 我们可以把java对象转成json 再存进去
  3. 应用
    • 缓存
    • 递增,减(点赞…)
    • ID增长
4.2使用string的问题

​ 假设有User对象以JSON序列化的形式存储到Redis中,User对象有id,username、password、age、name等属性,存储的过程如下: 保存、更新: User对象 ==> json(string) ==>redis

​ 如果在业务上只是更新age属性,其他的属性并不做更新我应该怎么做呢? 如果仍然采用上边的方法在传输、处理时会造成资源浪费,下边讲的hash可以很好的解决这个问题

知识点-Redis 哈希(Hash)

1.目标

  • 能够使用redis的hash操作命令

2.路径

  1. Redis 哈希(Hash)概述
  2. 应用场景
  3. 常见命令
  4. 应用举例

3.讲解

3.1 概述

​ Redis中hash 是一个键值对集合。

​ Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

​ Redis存储hash可以看成是String key 和String value的map容器. 也就是说把值看成map集合.

​ 它它特别适合存储对象相比较而言,将一个对象类型存储在Hash类型里要比存储在String类型里占用更少的内存空间并方便存取整个对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3NkrEBH-1688052369986)(img/image-20191223104457481.png)]

	key				value
	u1			name	zs
				age		18
				
	u2			name	ls
    			age		19
    			
    			Map<String,Map>
3.2应用场景

​ 用一个对象来存储用户信息,商品信息,订单信息等等。

3.3常见命令
命令 命令描述
hset key filed value 将哈希表 key 中的字段 field 的值设为 value
hmset key field1 value1 [field2 value2]…(重点) 同时将多个 field-value (字段-值)对设置到哈希表 key 中
hget key filed 获取存储在哈希表中指定字段的值
hmget key filed1 filed2 (重点) 获取多个给定字段的值
hdel key filed1 [filed2] (重点) 删除一个或多个哈希表字段
hlen key 获取哈希表中字段的数量
del key 删除整个hash(对象)
HGETALL key (重点) 获取在哈希表中指定 key 的所有字段和值
HKEYS key 获取所有哈希表中的字段
HVALS key 获取哈希表中所有值
3.4应用举例

存储商品信息

  • 商品字段【商品id、商品名称、商品价格】
  • 定义商品信息的key, 商品1001的信息在 Redis中的key为:[items:1001]
  • 存储商品信息
HMSET items:1001 id 3 name apple price 999.9 

4.小结

  1. Hash 是键值对存在的 类似Java里面的HashMap
  2. 特别适合存对象, 方便操作对象里面的某一个字段

知识点-Redis 列表(List)

1.目标

  • 能够使用redis的list操作命令

2.路径

  1. List类型
  2. Redis 列表(List)概述
  3. 应用场景
  4. 常见命令
  5. 应用举例

3.讲解

3.1List类型

​ ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。

​ LinkedList使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快。然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或后几个元素速度比较快。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cN9aKUpn-1688052369987)(img/wps9BB3.tmp.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3tofGvn-1688052369987)(img/wps9BB4.tmp.jpg)]

3.2概述

​ 列表类型(list)可以存储一个有序的字符串列表(链表),常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
​ 列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。

3.2应用场景

​ 如好友列表,粉丝列表,消息队列,最新消息排行等。

​ rpush方法就相当于将消息放入到队列中,lpop/rpop就相当于从队列中拿去消息进行消费

3.3.常见命令
命令 命令描述
lpush key value1 value2…(重点) 将一个或多个值插入到列表头部(左边)
rpush key value1 value2…(重点) 在列表中添加一个或多个值(右边)
lpop key(重点) 左边弹出一个 相当于移除第一个
rpop key(重点) 右边弹出一个 相当于移除最后一个
llen key 返回指定key所对应的list中元素个数
LINDEX key index 通过索引获取列表中的元素
LINSERT key BEFORE| AFTER pivot value 在列表的元素前或者后插入元素
3.4应用举例

商品评论列表

  • 思路: 在Redis中创建商品评论列表,用户发布商品评论,将评论信息转成json存储到list中。用户在页面查询评论列表,从redis中取出json数据展示到页面。

4.小结

  1. List是一个字符串链表,left、right都可以插入添加;
  2. 如果key不存在,创建新的链表;
    如果键已存在,新增内容;
    如果值全移除,对应的键也就消失了。
    链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就一般.

知识点-Redis 集合(Set)

1.目标

  • 能够使用redis的set操作命令

2.路径

  1. Redis 集合(Set)概述
  2. 应用场景
  3. 常见命令
  4. 应用举例

3.讲解

3.1概述

​ Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

​ Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的时间复杂度都是O(1)。集合中最大的成员数为 2的32次方 -1 (4294967295, 每个集合可存储40多亿个成员)。

​ Redis还提供了多个集合之间的交集、并集、差集的运算

​ 特点:无序+唯一

3.2应用场景

​ 投票记录

​ 共同好友、共同兴趣、分类标签

3.3.常见命令
命令 命令描述
sadd key member1 [member2] (重点) 向集合添加一个或多个成员
srem key member1 [member2] 移除一个成员或者多个成员
smembers key 返回集合中的所有成员,查看所有
SCARD key 获取集合的成员数
SPOP key 移除并返回集合中的一个随机元素
SDIFF key1 [key2] (重点) 返回给定所有集合的差集
SUNION key1 [key2] (重点) 返回所有给定集合的并集
SINTER key1 [key2] (重点) 返回给定所有集合的交集
3.4应用举例

共同好友

  • A的好友
  • B的好友
  • A和B的共同好友

4.小结

  1. Redis里面的Set 无效+唯一的, 类似Java里面的HashSet
  2. 应用
    • 投票的记录
    • 求差值

知识点-Redis 有序集合(sorted set)

1.目标

  • 能够使用redis的sorted set操作命令

2.路径

  1. sorted set介绍
  2. sorted set 应用场景
  3. sorted set 命令
  4. sorted set 具体示例

3.讲解

3.1概述

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

特点: 有序(根据分数排序)+唯一

3.2应用场景

排行榜:例如视频网站需要对用户上传的视频做排行榜.

3.3.常见命令
命令 命令描述
ZADD key score member [score member …] 增加元素
ZSCORE key member 获取元素的分数
ZREM key member [member …] 删除元素
ZCARD key 获得集合中元素的数量
ZRANGE key start stop[WITHSCORES] 获得排名在某个范围的元素列表
ZREVRANGE key start stop 按照分数从高到低排序
3.4应用举例

商品销售排行榜

  • 需求:根据商品销售量对商品进行排行显示
  • 思路:定义商品销售排行榜(sorted set集合),Key为items:sellsort,分数为商品销售量
  • 实现:
--商品编号1001的销量是9,商品编号1002的销量是10
ZADD items:sellsort 9 1001 10 1002		

--商品编号1001的销量加1
ZINCRBY items:sellsort 1 1001

--商品销量前10名()
ZREVRANGE items:sellsort 0 9

4.小结

  1. ZSet: 有序的Set
    • 特点: 有序+唯一的
  2. 应用场景: 排行榜

string、hash、list、set、zset

第三章-Redis通用的操作,发布订阅和持久化

知识点-Redis通用的操作

1.目标

  • 掌握Redis通用的操作命令

2.路径

  • 通用操作
  • 多数据库性

3.讲解

3.1 通用操作
  • keys *: 查询所有的key

  • exists key:判断是否有指定的key 若有返回1,否则返回0

  • expire key 秒数:设置这个key在缓存中的存活时间 (重要)

  • ttl key:展示指定key的剩余时间

    ​ 若返回值为 -1:永不过期

    ​ 若返回值为 -2:已过期或者不存在

  • del key:删除指定key (重要)

  • rename key 新key:重命名

  • type key:判断一个key的类型

  • ping :测试连接是否连接

3.2多数据库性

​ redis默认是16个数据库, 编号是从0~15. 【默认是0号库】

  • select index:切换库
  • move key index: 把key移动到几号库(index是库的编号)
  • flushdb:清空当前数据库
  • flushall:清空当前实例下所有的数据库

4.小结

  1. exists key 判断是否有这个key
  2. expire key 秒数 设置这个key在缓存中的存活时间
  3. ttl key 展示指定key的剩余时间
  4. select index 切换库

知识点-订阅发布机制【了解】

1.目标

  • 了解Redis订阅发布机制

2.路径

  1. 什么是Redis订阅发布机制
  2. 相关的命令
  3. 订阅发布实操

3.讲解

3.1什么是Redis订阅发布机制

​ Redis 发布订阅(pub/sub)是进程间一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

​ Redis 客户端可以订阅任意数量的频道。

3.2相关的命令
序号 命令及描述
1 PUBLISH channel message 将信息发送到指定的频道。
2 SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息。
3 UNSUBSCRIBE [channel [channel …]] 指退订给定的频道
3.3订阅发布实操
  • 开启两个客户端
  • A客户端
SUBSCRIBE nba
  • B客户端
PUBLISH  nba aaa

4.小结

  1. Redis 发布订阅(pub/sub)是进程间一种消息通信模式, 工作里面一般使用MQ

知识点-Redis的持久化【面试】

1.目标

​ Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘(文件)中,这一过程就是持久化

​ Redis支持两种方式的持久化,一种是RDB方式,一种是AOF方式。可以单独使用其中一种或将二者结合使用。

2.路径

  • RDB持久化机制
  • AOF持久化机制
  • 两种持久化机制比较

3.讲解

3.1RDB持久化机制
3.1.1概述

​ RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。 这种方式是默认已经开启了,不需要配置.

3.1.2 RDB持久化机制的配置
  • 在redis.windows.conf配置文件中有如下配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6gmQH9M-1688052369988)(img/tu_14-1571712041246.png)]

其中,上面配置的是RDB方式数据持久化时机:

关键字 时间(秒) key修改数量 解释
save 900 1 每900秒(15分钟)至少有1个key发生变化,则dump内存快照
save 300 10 每300秒(5分钟)至少有10个key发生变化,则dump内存快照
save 60 10000 每60秒(1分钟)至少有10000个key发生变化,则dump内存快照
3.2 AOF持久化机制
3.2.1概述

​ AOF持久化机制会将每一个收到的写命令都通过write函数追加到文件中,默认的文件名是appendonly.aof。 这种方式默认是没有开启的,要使用时候需要配置.

3.2.2AOF持久化机制配置
3.2.2.1开启配置
  • 在redis.windows.conf配置文件中有如下配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRRyGcFb-1688052369989)(img/tu_15-1571712041246.png)]

  • 将appendonly修改为yes, 但是启动redis的时候需要指定该文件,也就是意味着不能直接点击了, 需要输入命令启动:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYbseETc-1688052369989)(img/tu_12-1571712041245.png)]

  • 开启aof持久化机制后,默认会在目录下产生一个appendonly.aof文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFvcT3E7-1688052369990)(img/tu_13-1571712041246.png)]

3.2.2.2配置详解
  • 上述配置为aof持久化的时机,解释如下:(在redis.windows.conf配置)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2BCPhWL-1688052369990)(img/tu_16-1571712041246.png)]

关键字 持久化时机 解释
appendfsync always 每执行一次更新命令,持久化一次
appendfsync everysec 每秒钟持久化一次
appendfsync no 不持久化

4,小结

4.1RDB
优点
  • RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份
  • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快(因为其文件要比AOF的小)
  • RDB的性能要比AOF更好
缺点
  • RDB的持久化不够及时(一定时间间隔),可能会存在数据丢失
  • RDB持久化时如果文件过大可能会造成服务器的阻塞,停止客户端请求
4.2AOF
优点
  • AOF的持久性更加的耐久(可以每秒 或 每次操作保存一次)
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。
  • AOF是增量操作
缺点
  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB.
4.3选择
  • 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,选择RDB 持久化。
  • 如果对数据的完整性要求比较高, 选择AOF

第四章-Jedis (重点)

案例-Jedis的快速入门

1.目标

  • 掌握什么是Jedis

2.路径

  1. jedis的介绍

  2. Jedis的入门

3.讲解

3.1 jedis的介绍

​ Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。 在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,Jedis同样也是托管在github上.

说白了Jedis就是使用Java操作Redis的客户端(工具包)

地址:https://github.com/xetorthio/jedis。

文档地址:http://xetorthio.github.io/jedis/

方法 解释
new Jedis(host, port) 创建jedis对象,参数host是redis服务器地址,参数port是redis服务端口
set(key,value) 设置字符串类型的数据
get(key) 获得字符串类型的数据
hset(key,field,value) 设置哈希类型的数据
hget(key,field) 获得哈希类型的数据
lpush(key,values) 设置列表类型的数据
lpop(key) 列表左面弹栈
rpop(key) 列表右面弹栈
sadd(String key, String… members) 设置set类型的数据
zrange(String key, long start, long end) 获得在某个范围的元素列表
del(key) 删除key
3.2 Jedis的入门

需求: 使用java代码操作Redis 进行增(改)删查

步骤:

  1. 导入jar
  2. 创建Jedis对象
  3. 使用方法操作
  4. 关闭资源
  • 基本操作
@Test
public void test01(){
    
    
    String host = "localhost";
    int port = 6379;
    Jedis jedis = new Jedis(host, port);

    //往redis服务器中存储 username :jay
    jedis.set("username","jay");

    //关闭连接
    jedis.close();
}

@Test
public void test02(){
    
    
    //实现:使用jedis获取redis服务器中存储的username的值
    String host = "localhost";
    int port = 6379;
    Jedis jedis = new Jedis(host, port);

    //调用get()方法
    String username = jedis.get("username");
    System.out.println(username);

    //关闭连接
    jedis.close();
}

@Test
public void test03(){
    
    
    //使用jedis往redis服务器中存储一个hash类型的数据
    String host = "localhost";
    int port = 6379;
    Jedis jedis = new Jedis(host, port);

    Map<String,String> map = new HashMap<>();
    map.put("username","aobama");
    map.put("age","90");
    map.put("address","usa");
    map.put("sex","male");
    //调用hmset()
    jedis.hmset("hkey1",map);

    //关闭
    jedis.close();
}

@Test
public void test04(){
    
    
    //使用jedis往redis数据库中存储list类型的数据
    String host = "localhost";
    int port = 6379;
    Jedis jedis = new Jedis(host, port);

    //调用lpush()
    jedis.lpush("skey1","a","b","c","d");

    jedis.close();
}

@Test
public void test05(){
    
    
    //使用jedis往redis中存储set类型的数据
    String host = "localhost";
    int port = 6379;
    Jedis jedis = new Jedis(host, port);

    //调用sadd()方法
    jedis.sadd("skey1","a","b","c","d","e","f","a","a","b","d","d");

    jedis.close();
}

4.小结

  1. Jedis: java操作Redis的客户端, 工具包
  2. 使用步骤
    • 导入jar
    • 创建jedis对象
    • 调用方法
    • 释放资源

知识点-Jedis进阶

1.目标

  • 掌握Jedis连接池的使用

2.路径

  1. jedis连接池介绍
  2. jedis连接池的使用
  3. 工具类的抽取

3.讲解

3.1 jedis连接池的基本概念

​ jedis连接资源的创建与销毁是很消耗程序性能,所以jedis为我们提供了jedis的池化技术,jedisPool在创建时初始化一些连接资源存储到连接池中,使用jedis连接资源时不需要创建,而是从连接池中获取一个资源进行redis的操作,使用完毕后,不需要销毁该jedis连接资源,而是将该资源归还给连接池,供其他请求使用。

3.2jedis连接池的使用

需求: 从Jedis的连接池里面获得jedis

步骤:

  1. 创建JedisPool配置对象
  2. 创建JedisPool对象
  3. 从JedisPool获得jedis
  4. 操作Redis
  5. 释放资源
  • 基本使用
@Test
public void test06(){
    
    
    //创建连接池的配置对象,进行一些属性的配置
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMaxTotal(20);
    jedisPoolConfig.setMaxIdle(12);
    jedisPoolConfig.setMaxWaitMillis(3000);
    String host = "localhost";
    int port = 6379;
    //使用jedis连接池
    JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port);

    //从连接池中获取连接
    Jedis jedis = jedisPool.getResource();

    //调用jedis的方法操作redis服务器
    String username = jedis.get("username");
    System.out.println(username);

    //调用jedis的close()方法,归还连接
    jedis.close();
}
3.3Jedis工具类的抽取

目的: 1.保证连接池只有一个 2.简化获得jedis对象的代码

步骤:

  1. 创建jedis.properties配置文件
  2. 创建JedisUtils类
  3. 定义JedisPool, 在静态代码块读取配置文件,并且初始化JedisPool兑现
  4. 创建getJedis()方法从JedisPool获得Jedis
  5. 创建close()方法归还
package com.itheima.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ResourceBundle;

/**
 * 包名:com.itheima.utils
 *
 * @author Leevi
 * 日期2020-07-26  14:36
 * Jedis的工具类
 */
public class JedisUtil {
    
    
    private static JedisPool jedisPool;
    private static String host;
    private static int port;
    private static int maxTotal;
    private static int maxIdle;
    private static int maxWaitMillis;
    static {
    
    
        //读取jedis.properties配置文件
        ResourceBundle resourceBundle = ResourceBundle.getBundle("jedis");
        host = resourceBundle.getString("host");
        port = Integer.parseInt(resourceBundle.getString("port"));
        maxTotal = Integer.parseInt(resourceBundle.getString("maxTotal"));
        maxIdle = Integer.parseInt(resourceBundle.getString("maxIdle"));
        maxWaitMillis = Integer.parseInt(resourceBundle.getString("maxWaitMillis"));

        //创建连接池
        //创建连接池的配置对象,进行一些属性的配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxTotal);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        //使用jedis连接池
        jedisPool = new JedisPool(jedisPoolConfig,host,port);
    }

    /**
     * 获取连接的方法
     * @return
     */
    public static Jedis getJedis(){
    
    
        return jedisPool.getResource();
    }
}

4.小结

  1. 使用JedisPool目的: 为了jedis复用

案例-使用Redis优化省份的展示

1.需求

​ 访问index.html页面,使用ajax请求加载省份列表(响应json)

  • 先从Redis里面获得
    • 有 就直接返回
    • 没有 从Mysql获得,再存到Redis

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YSPAw3Fd-1688052369991)(img/1571210295308.png)]

2.分析

2.1直接从MySQL获得
  1. 创建数据库, 创建web工程(页面, jar, 工具类, 配置文件)
  2. 创建vue实例, 在created 钩子函数里面
axios.get('province').then(function(response){
	//获得数据赋值, 绑定
})
  1. 创建ProvinceServlet
//1.调用业务 获得List<Province> list
//2.把list转成json响应
  1. 创建ProvinceService
public List<Province> findAll(){
	//调用Dao
}
  1. 创建ProvinceDao
2.2优化的思路
  1. 先从Redis里面获得
    • 如果有 ,直接返回
    • 如果没有, 从Mysql获得,再存到Redis

3.代码实现

3.1准备工作
  • 数据库
CREATE TABLE `province` (
  `pid` int NOT NULL AUTO_INCREMENT,
  `pname` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

INSERT INTO `province` VALUES ('1', '广东');
INSERT INTO `province` VALUES ('2', '湖北');
INSERT INTO `province` VALUES ('3', '湖南');
INSERT INTO `province` VALUES ('4', '四川');
INSERT INTO `province` VALUES ('5', '山东');
INSERT INTO `province` VALUES ('6', '山西');
INSERT INTO `province` VALUES ('7', '广西');
  • 创建工程(web)
  • 导入jar包, 导入配置文件, 导入工具类,导入页面
  • Province.java
package com.itheima.pojo;

import java.io.Serializable;

/**
 * 包名:com.itheima.pojo
 *
 * @author Leevi
 * 日期2020-07-26  15:10
 */
public class Province implements Serializable {
    
    
    private Integer pid;
    private String pname;

    @Override
    public String toString() {
    
    
        return "Province{" +
                "pid=" + pid +
                ", pname='" + pname + '\'' +
                '}';
    }

    public Integer getPid() {
    
    
        return pid;
    }

    public void setPid(Integer pid) {
    
    
        this.pid = pid;
    }

    public String getPname() {
    
    
        return pname;
    }

    public void setPname(String pname) {
    
    
        this.pname = pname;
    }
}
3.2代码实现
  • 页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>省份展示页面</title>
    <script src="js/axios-0.18.0.js"></script>
    <script src="js/vuejs-2.5.16.js"></script>
</head>
<body>
    <div id="app">
        <select id="province">
            <option>请选择省份</option>
            <option v-for="(province,index) in provinceList" :value="province.pid" v-text="province.pname"></option>
        </select>
    </div>
    <script>
        var vue = new Vue({
      
      
           el:"#app",
           data:{
      
      
                provinceList:[]
           },
           methods:{
      
      
                findAll(){
      
      
                    //获取所有省份信息
                    axios.get("/province?action=findAll").then(response=>{
      
      
                        //获取返回的数据中的flag
                        if (response.data.flag) {
      
      
                            //查询成功
                            this.provinceList = response.data.data
                        }else {
      
      
                            //查询失败
                            alert("获取省份失败")
                        }
                    })
                }
           },
           created(){
      
      
                this.findAll()
           }
        });
    </script>
</body>
</html>
  • ProvinceServlet
package com.itheima.web.servlet;

import com.itheima.entry.ResultBean;
import com.itheima.pojo.Province;
import com.itheima.service.ProvinceService;
import com.itheima.utils.JsonUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 包名:${PACKAGE_NAME}
 *
 * @author Leevi
 * 日期2020-07-26  15:07
 */
@WebServlet("/province")
public class ProvinceServlet extends HttpServlet {
    
    
    private ProvinceService provinceService = new ProvinceService();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        //1. 获取请求参数action的值
        String action = request.getParameter("action");
        try {
    
    
            Class clazz = this.getClass();
            //根据方法名获取方法
            Method method = clazz.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            //调用方法
            method.invoke(this,request,response);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 查询所有
     * @param request
     * @param response
     */
    public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
    
        ResultBean resultBean = new ResultBean(true);
        try {
    
    
            //1. 调用业务层的方法,查询所有联系人
            List<Province> provinceList = provinceService.findAll();
            //2. 将要响应的数据封装到ResultBean对象中
            resultBean.setData(provinceList);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            resultBean.setFlag(false);
            resultBean.setErrorMsg("查询失败");
        }

        //将resultBean对象转换成json字符串
        JsonUtils.printResult(response,resultBean);
    }
}
  • ProvinceService
package com.itheima.service;

import com.alibaba.fastjson.JSON;
import com.itheima.dao.ProvinceDao;
import com.itheima.pojo.Province;
import com.itheima.utils.JedisUtil;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
 * 包名:com.itheima.service
 *
 * @author Leevi
 * 日期2020-07-26  15:10
 */
public class ProvinceService {
    
    
    private ProvinceDao provinceDao = new ProvinceDao();
    public List<Province> findAll() throws Exception {
    
    
        //1. 从redis服务器中获取所有省份信息
        Jedis jedis = JedisUtil.getJedis();
        String province_list = jedis.get("province_list");
        //2. 判断province_list是否为null
        if (province_list == null) {
    
    
            //说明,redis中还没有缓存
            //调用dao层的方法,到mysql数据库中查询出所有省份信息
            List<Province> provinceList = provinceDao.findAll();
            //将provinceList转换成json字符串,然后存储到redis服务器中
            province_list = JSON.toJSONString(provinceList);
            jedis.set("province_list",province_list);
        }

        //归还连接
        jedis.close();

        //我们要返回给Servlet的是List<Province>,所以要将province_list转换成List<Province>
        return JSON.parseArray(province_list, Province.class);
    }
}
  • ProvinceDao
package com.itheima.dao;

import com.itheima.pojo.Province;
import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.util.List;

/**
 * 包名:com.itheima.dao
 *
 * @author Leevi
 * 日期2020-07-26  15:12
 */
public class ProvinceDao {
    
    
    private QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
    public List<Province> findAll() throws Exception {
    
    
        String sql = "select * from province";
        List<Province> provinceList = queryRunner.query(sql, new BeanListHandler<>(Province.class));
        return provinceList;
    }
}

4.小结

  1. 优化
    • 先从Redis获得
      • 有, 直接返回
      • 没有. 从Mysql获得 再存到Redis

案例-使用Redis完成邮箱验证码的校验

1.需求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Idf4mDyN-1688052369993)(img/code.png)]

  • 输入邮箱, 点击发送发送验证码邮件
  • 点击提交向服务器提交验证码进行校验

2.分析

2.1发送邮件思路
客户端代码
sendEmail(){
    
    
    axios.post("/check?email="+this.email).then(response=>{
    
    
        if (response.data.flag) {
    
    
            //发送成功
            this.msg = "发送成功"
        }else {
    
    
            //发送失败
            this.msg = "发送失败"
        }
    })
}
Servlet的代码
package com.itheima.web.servlet;

import com.itheima.entry.ResultBean;
import com.itheima.utils.JedisUtil;
import com.itheima.utils.JsonUtils;
import com.itheima.utils.MailUtil;
import redis.clients.jedis.Jedis;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Random;

/**
 * 包名:${PACKAGE_NAME}
 *
 * @author Leevi
 * 日期2020-07-26  15:46
 */
@WebServlet("/check")
public class CheckCodeServlet extends HttpServlet {
    
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        ResultBean resultBean = new ResultBean(true);
        try {
    
    
            //1. 获取要发送验证码的邮箱地址
            String email = request.getParameter("email");
            //2. 创建一个随机的验证码
            //创建一个验证码字符串,总共有四位
            String base = "0123456789ABCDEFGabcdefg";
            int size = base.length();
            Random r = new Random();
            StringBuffer sb = new StringBuffer();
            for(int i=1;i<=4;i++){
    
    
                //产生0到size-1的随机值
                int index = r.nextInt(size);
                //在base字符串中获取下标为index的字符
                char c = base.charAt(index);
                //将c放入到StringBuffer中去
                sb.append(c);
            }

            String code = sb.toString();//获取验证码

            //3. 将验证码code存储到redis服务器中,并且指定过期时间
            Jedis jedis = JedisUtil.getJedis();
            jedis.setex("code",60*2,code);

            jedis.close();

            //4. 发送邮件
            MailUtil.sendMail(email,"验证码是:"+code+",请在两分钟之内使用");
        } catch (Exception e) {
    
    
            e.printStackTrace();
            resultBean.setFlag(false);
        }

        //将resultBean转换成json字符串输出
        JsonUtils.printResult(response,resultBean);
    }
}
2.2验证思路
客户端代码
register(){
    
    
    axios.post("/register?checkCode="+this.checkCode).then(response=>{
    
    
        if (response.data.flag) {
    
    
            //注册成功
            alert("注册成功")
        }else {
    
    
            //注册失败
            alert("注册失败")
        }
    })
}
Servlet代码
package com.itheima.web.servlet;

import com.itheima.entry.ResultBean;
import com.itheima.utils.JedisUtil;
import com.itheima.utils.JsonUtils;
import redis.clients.jedis.Jedis;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 包名:${PACKAGE_NAME}
 *
 * @author Leevi
 * 日期2020-07-26  15:47
 */
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
    
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        ResultBean resultBean = new ResultBean(true);
        try {
    
    
            //1. 获取客户端传入的验证码
            String checkCode = request.getParameter("checkCode");
            //2. 从redis中获取服务器端生成的验证码
            Jedis jedis = JedisUtil.getJedis();
            String code = jedis.get("code");

            jedis.close();
            //3. 校验验证码
            if (!checkCode.equalsIgnoreCase(code)) {
    
    
                //不通过
                resultBean.setFlag(false);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
            resultBean.setFlag(false);
        }

        JsonUtils.printResult(response,resultBean);
    }
}

猜你喜欢

转载自blog.csdn.net/NeverFG/article/details/131466542
今日推荐