Redis 6 知识点总结

Posted 执章学长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 6 知识点总结相关的知识,希望对你有一定的参考价值。

Redis 6 知识点总结

什么是 NoSQL 数据库?

技术发展

技术的分类

我们学习过很多的技术,但根据技术的功能可以分为以下几种:

功能型技术:解决基础的功能,没有它们根本完成不了项目。例如 JavaSE,html 等。
扩展型技术:没有这些技术依然可以开发项目,但有了它们可以简化开发流程。例如:Spring、MyBatis 等。
性能型技术:没有这些技术依然可以开发项目,但为了提高项目的性能,不得不使用这些技术。例如:Redis、MQ 等。

Web 开发分类

Web 1.0 时代
在早期,项目基本是这种架构,那时候上网的人不是特别多,因此这种架构能够承担得起压力。

Web 2.0 时代
到了后来,上网的人越来越多,还出现了很多手机端的用户,这个时候服务器和数据库根本承受不住这么大的访问量。

出现的问题:服务器要处理大量的数据,导致CPU及内存压力过大,数据库要频繁写入,导致IO压力过大。

因此在 Web 2.0 时代通过 NoSQL 解决这两个问题。
1、解决 CPU 和内存问题
要解决 CPU 和内存的压力,我们最容易想到的就是增加多台服务器,然后通过 nginx 技术均衡负载到各个服务器,通俗得讲就是假如你的公司一个人干活干不过来,这时候就需要雇多个人来干活,那么我们需要尽量均衡的分配任务给每个人。

但这个方案会带来另一个问题:如何实现共享 Session ?我们知道,Session 存储在服务器中,那么当一个用户1访问服务器 A 时,将 Session 信息存储在服务器 A 中,那么下次,他发起请求被分到 服务器 B 处理,这个时候 B 并没有用户的 Session。

解决方案一:使用 cookie 存储。
因为 cookie 是存在客户端的,可以解决。但弊端是数据存储在客户端是不安全的。
解决方案二:服务器之间进行 Session 复制。
以上面的例子,但服务器 A 有了用户1的 Session,就复制(同步)给其他服务器,但这样会使得 Session 数据冗余(同个 Session 有多份)。
解决方案三:使用单独的服务器存储(存储在 NoSQL 中) Session 信息,当请求到达时,先查看 Session 服务器是否有该用户的 Session 。好处:使用 NoSQL 不用进行 IO 操作,数据存到内存中,读取速度更快。

2、解决 IO 压力
解决方案一:水平、垂直切分数据库、读写分离等方案,目的是降低数据库的读写压力。但弊端是这样做会破坏一定的业务逻辑
解决方案二:在服务器和数据库之间加一层缓存数据库,降低读的压力。

各种数据库对比

NoSQL 数据库

NoSQL 数据库是指非关系型数据库,存储时以键值对存储,不遵循 SQL 标准不支持 ACID性能远超 SQL

NoSQL 是一类数据库的总称,包括 Redis 、 Memeache 、 MongoDB 等。

Memcache 和 Redis 的区别?

Memcache 是比较早期的 NoSQL 数据库,数据存储在内存中,不支持持久化,数据以键值对存储,但类型单一(只支持 String )。
Redis 数据同样是存储在内存中,但支持持久化(用于备份和恢复),支持多种数据结构(string、list、set、hash、zset 等)。

其它数据库

1、行式数据库
每一行存储在一个数据结构中。
2、列式数据库
每一列存储在一个数据结构中。
3、图关系型数据库
用于存储社交关系网络、地图、交通信息等等。

Redis 简介与安装

Redis 简介

引入了 NoSQL 的概念之后,我们来了解一下什么是 Redis ?

  • 开源、基于键值对存储
  • 支持多种数据类型,包括 string(字符串)、list(链表)、set(集合)、zset(有序集合,也叫sort set)和 hash(哈希)。
  • 每种数据类型都支持 push 、 pop 、 add 、 remove、取交集、取并集、取差集等操作。
  • 单个操作都是原子性的。
  • 支持对数据进行排序
  • 存储在内存中
  • 支持持久化(定期写入磁盘 或 追加记录文件)

Redis 应用场景

1、配合关系型数据库做高速缓存
存储热点数据,降低关系型数据库的 IO 次数
分布式架构中的共享 Session

2、利用 Redis 多样的数据结构做特殊的功能
利用 zset 的有序性做排行榜(top n)
利用排序功能展示最新数据
利用 expire 设置过期时间存储手机验证码
利用原子性实现计数器
利用 set 的不可重复性做去重
利用 list 模拟队列
利用 pub/sub 模式实现订阅消息系统

Redis 的下载

1、打开 Redis 的官网
Redis 官网
2、点击右上角的「Download」

3、可以看到最新的版本

4、在这个页面往下滑动(可以看到最新的稳定版本的链接)

5、再往下滑动有较早的版本

6、选择自己合适的版本就可以下载xx.tar.gz压缩文件。

官方只提供 Linux 版本的 Redis,想要下载 Windows 版本可以通过到微软的专门开发组织那里下载,地址如下:https://github.com/MicrosoftArchive/redis

Redis 的安装

如果目前是在 Windows 系统下操作,我们可以先连接远程 Linux 服务器(也可以通过连接虚拟机等),接着将压缩文件上传到服务器的任意位置(只是一个压缩包,放哪里都可以)。假设我们现在把压缩包放在/opt下。

1、检查是否有 C 语言的编译环境

gcc --version

2、如果提示没有找到命令,那么说明没有安装,这个时候就需要安装 C 语言的编译环境(需要连网)(安装完成后可以使用1中的命令检查是否安装完成)

yum install gcc

3、解压压缩文件

tar -zxvf 压缩文件名

4、解压完成后会生成一个redis-版本号的文件夹,进入这个文件夹

cd redis-版本号

5、因为刚刚下载好了 C 的编译环境,现在可以用 make 命令进行编译成 c 文件啦~(如果报错的话,先执行 make distclean 再执行 make 命令)

make

6、进行安装(安装成功后,默认会安装到/usr/local/bin路径下)

make install

7、进入该目录

cd /usr/local/bin

8、显示文件列表(可以看到有各种 redis 文件,表示安装成功)

ll

Redis 的启动

Redis 前台启动(不推荐)

缺点:一旦关闭窗口,Redis 就会关闭
启动方式:

redis-server

启动后默认端口:6379
退出:直接按 Ctrl + C

Redis 后台启动

优点:关闭窗口,Redis 仍然是启动状态
1、进入到解压后的文件夹中,可以看到有一个redis.conf文件

cd redis-版本号

2、将该文件复制到etc目录下

cp redis.conf /etc/redis.conf

3、来到ect目录下

cd /ect

4、修改etc目录下的redis.conf文件,把daemonize no改成yes,表示支持后台启动

进入该文件

vi redis.conf

定位

/daem

进入编辑模式进行修改

i

修改后按 esc 进入命令行模式

保存并退出

:wq

5、进入到/usr/local/bin路径下,启动(注意:是etc 下的 conf ,因为我们刚刚修改过)

redis-server /etc/redis.conf

6、启动后可以查看 redis 进程

ps -ef | grep redis

可以看到已经启动了,并且默认的端口是 6379

7、客户端启动 Redis

redis-cli

8、关闭客户端

exit

9、关闭 Redis 服务
方式一:在客户端状态下

shutdown

方式二:不在客户端状态下

# 查找到进程号
ps -ef | grep redis
# 直接kill掉进程
kill -9 进程号

Redis 的相关知识

1、Redis 默认有几个数据库?
Redis 默认有16个数据库,下标从 0 开始递增,初始默认使用的是 0 号库,可以使用select 库号来切换数据库,并且所有库的密码相同。

# 客户端启动 redis
redis-cli
# 修改当前使用的库为1 号库
select 1

2、Redis 的单线程+多路 IO 复用技术

串行:类似羊肉串,一个一个的吃
多线程+锁:多个线程并发处理,通过对每个操作加锁。(memcached)
单线程+多路 IO 复用:(redis)

Redis 常用五大数据类型

Redis 中对键(key)的操作

查看当前库中的所有 key

keys *

判断某个 key 是否存在,返回1表示存在,0表示不存在

exists 键名

判断某个 key 是什么类型(返回 string等等)

type 键名

删除指定的某个 key

del 键名

根据 value 选择非阻塞删除(效果同上,可以删除键值对,但是是先将 key 先从 keyspace 元数据中删除,真正的删除会在后续异步操作)

unlink 键名

为给定的 key (该键值对已经存在)设置过期时间为10秒钟

expire 键名 10

查看指定的 key 还有多少秒过期,-1表示永不过期,-2 表示已经过期

ttl 键名

切换数据库

select 数据库编号

查看当前数据库的key数量

dbsize 

清空当前库

flushdb

清空所有库

flushall

Redis 中 String 类型

简介

格式:key value
一个 key 对应一个 value,value 的最大值为 512 M,String 类型是二进制安全的,意味着可以存储 jpg 格式的图片或者序列化对象。
特点:单键单值,单值部分为 String

常见命令

添加键值对(键已经存在时会更新值得内容,不存在则会创建键值对)

set 键名 值

按照键查找值,返回值

get 键名

在某个键值对的值后面追加(例如有个键值对 k1 5,追加 append k1 7,该键值对就会变成 k1 57)

append 键名 追加值

根据键返回值的长度

strlen 键名

添加键值对(键不存在时会创建键值对,存在则什么都不做)

setnx 键名 值

将 key 中存储的数字值 + 1

incr 键名

将 key 中存储的数字值 - 1

decr 键名

将 key 中存储的数字值 + 步长

incrby 键名 步长

将 key 中存储的数字值 - 步长

decrby 键名 步长

注意:由于 Redis 是单线程的,所以 Redis 单命令的操作具有原子性(注意:这里的原子性和事务的原子性不是一个概念),因此 Redis 的 incrby 命令自增时是原子性的,对比 Java 中的 i++ 操作,在多线程情况下不具备原子性(经典案例:对于变量 a = 0,线程1执行a+=100,线程2执行a+=100,结果a的值<=100)。

同时设置多个键值对

mset 键1 值1 键2 值2...

根据多个键获取多个值

mget 键1 键2 键3...

同时设置多个键值对(只有该键值对不存在时才会设置。并且具有原子性,一个创建失败那就全部失败)

msetnx 键1 值1 键2 值2...

获取某个键值对的值的一部分
例如:set name lucymary
getrange name 0 3 会获得 lucy

getrange 键名 开始索引(包括) 结束索引(包括)

设置指定位置的值
例如:set name lucymary
setrange name 3 abc
get name
获得 lucabcry

setrange 键名 起始位置索引 值

设置键值对,并赋予过期时间(与之前不同,现在时一步操作),过期时间单位是秒

setex 键名 过期时间 值

根据键获取值,并修改值(此时会返回旧值,然后更新新值,之后再执行 get 就会返回新值)

getset 键名 值

Redis 中 List 类型

简介

特点:单键多值 ,多值部分为一个有序可重复列表 List
底层:双向链表,因此,List 具有双向链表的特点,例如:首尾操作效率高,按索引插入效率低等。

List 底层的数据结构

1、当 List 中数据量较少的时候,这些数据会存储在一块连续的内存中,我们把这块连续的内存叫做 压缩列表 ZipList
2、当 List 中的数据量越来越多的时候,我们将各个 ZipList 用双向链表的形式存储起来,这种结构叫做 快速链表 QuickList

常见命令

根据键向列表左边插入一个或多个值

lpush 值1 值2...

根据键向列表右边插入一个或多个值

rpush 值1 值2...

根据键从列表左边吐出一个或多个值

lpop 值1 值2...

根据键从列表右边吐出一个或多个值

rpop 值1 值2...

从键1列表的右边吐出一个值,插入到键2的左边

rpoplpush 键1 键2

按照索引获取一个键中的元素(从左到右)
lrange 键名 0 -1 表示取出整个list

lrange 键名 开始索引 结束索引

按照索引获取某个键对应列表的该下标元素

index 键名 索引

获取列表的长度

llen 键名

在某个值的前面插入新值

linsert 键名 before 值 新值

在某个值的后面插入新值

linsert 键名 after 值 新值

删除列表左边的 n 个该值

lrem 键名 n 值名

将列表下标为 该索引 的值替换为新值

lset 键名 索引 新值

注意:所有操作 list 的过程中,如果某个 list 的值的个数已经为0,那么这个键值对就不存在了

Redis 中 Set 类型

简介

特点:单键多值 ,多值部分为一个无序不可重复集合 Set
底层:value 为 固定值的哈希表,因此,Set 具有 哈希表的特点,例如:添加,删除,查找的效率都是 O(1)。

常见命令

添加一个或多个值到一个 set 中(由于不可重复的特点,已经存在的元素则会忽略)

sadd 键名 值1 值2 值3...

取出一个 set 中的所有值

smembers 键名

判断 set 中是否含有指定的值,有返回1,没有返回0

sismember 键名 值

返回 Set 中元素个数

scard 键名

删除集合中的指定元素

srem 键名 值1 值2...

随机从集合中吐出一个值(会删除)

spop 键名

随机从集合中取出 n 个值(不会删除)

srandmember 键名 n

把集合1中的某个值移动到另一个集合2(移出的那个集合1会删除掉该值)

smove 键1 键2 值

返回两个集合的交集元素

sinter 键1 键2

返回两个集合的并集元素

sunion 键1 键2

返回两个集合的差集元素(即键1中有的,但键2 中没有的

sdiff 键1 键2

Redis 中 Hash 类型

简介

特点:单键多值 ,多值部分为一个 Map 结构,因此,整个 key - value 类似 Java 中的 Map<String,Object>。 所以,特别适合用于存储 Java 对象。例如:登录时需要存储用户信息,我们可以把用户信息封装成一个对象(包括姓名、年龄、电话等),然后将用户的唯一标识(一般为用户 ID )存储为 key ,将封装的对象存储为 value。

底层:value 为 固定值的哈希表,因此,Set 具有 哈希表的特点,例如:添加,删除,查找的效率都是 O(1)。

Hash 底层的数据结构

当 field - value 长度较短且个数较少时,会使用 ZipList(压缩列表);否则,会使用 HashTable(哈希表)。

常见命令

Hash 类型的一个键值对如下图,一个键中的一个字段叫做 field(领域)。

key - field1 value1
	  field2 value2
	  field3 value3

给一个键添加一个 field 并赋值(已经存在该领域则会更新值)

hset 键名 领域名 值

给一个键添加一个 field 并赋值(已经存在该领域则不会执行)

hsetnx 键名 领域名 值

取出一个键中的一个领域

hget 键名 领域名

给一个键批量添加 field

hmset 键名 领域名1 值1 领域名2 值2...

查看某个 领域 是否存在于某个键中

hexists 键名 领域名

列出一个键中的所有领域

hkeys 键名

列出一个键中的所有值

hvals 键名

为指定键指定领域的值加上指定增量

hincrby 键名 领域名 增量

Redis 中 Zset(Sorted Set) 类型

简介

==特点:单键多值,多值部分为一个有序不可重复集合,有序是通过维护一个 score (分数)来实现,根据分数的大小进行排序。==所以,Zset 适合做排行版功能。

Zset 底层的数据结构

底层的数据结构: Hash + 跳跃表 实现。

前面学到,Hash 的 field 是不重复的,正好对应 Zset 的值,而 field 对应的 值,正好对应 Zset 的分数。而跳跃表主要用于实现排序和根据分数范围获取元素等功能。

普通有序链表:

跳跃表:

结论:已排序链表中,跳跃表查找效率高于普通链表。

常见命令

将一个或多个元素及其分数添加到一个 key 中

zadd 键名 分数1 值1 分数2 值2...

返回一个 key 中,下标在 开始索引 到 结束索引 之间的值(按照从小到大排序,可以在后面加上withscores会显示他们的分数)

zrange 键名 开始索引 结束索引

返回一个 key 中,下标在 开始索引 到 结束索引 之间的值(按照从大到小排序,可以在后面加上withscores会显示他们的分数)

zrevrange 键名 开始索引 结束索引

返回一个 key 中,分数在 指定的较小分数 和 指定的较大分数 之间的元素,并按照分数从小到大排序(包括分数为 指定的较小分数 和 指定的较大分数 的值)

zrangebyscore 键名 指定的较小分数 指定的较大分数

返回一个 key 中,分数在 指定的较大分数 和 指定的较小分数 之间的元素,并按照分数从大到小排序(包括分数为 指定的较大分数 和 指定的较小分数 的值)

zrevrangebyscore 键名 指定的较大分数 指定的较小分数

为指定键的指定值的分数加上增量

zincrby 键名 增量 值

删除这个 key 下指定值的元素

zrem 键名 值

统计这个 key 中,指定分数区间内的元素个数

zcount 键名 指定分数较小值 指定分数较大值

返回这个值在这个 key 中的排名(从 0 开始)

zrank 键名 值

场景:如何利用 zset 实现一个文章的访问量排行榜? 答:可以使用zrevrange命令。

Redis 发布和订阅

频道:类似生活中的电视台
发布者:负责向频道发送消息
订阅者:可以提前订阅某个频道,当频道有消息时,会收到消息。

1、订阅者1订阅频道1
2、发布者1发布消息到频道1
3、订阅者收到频道1的消息

操作方法:
1、打开两个 Redis 客户端(模拟订阅者和发布者)
2、订阅者订阅频道 channel1

suescribe channel1

3、发布者发布消息 hello 到 channel1

publish channel1 hello

4、订阅者收到消息

Redis 中的新数据类型

Bitmaps

简介


Bitmaps 本身不是一种数据类型,实际上它是字符串(即 key - value 形式存储),但是它可以对字符串的位进行操作。可以把Bitmaps理解为把存进去的数据转换为二进制存储,这样每个位上就要么是0,要么是1。如下图:

使用场景:
1、统计某个用户是否访问过此网站
2、签到功能

缺点:例如我现在的用户 id 是1万,如果我将 id 作为偏移量进行存储,且我只有一个用户时,前面的九千多个位存着 0 实际上时浪费的,这个时候可以选择不用 Bitmaps ,或者将 id 减少 9999 ,然后再存储。

在第一次初始化 Bitmaps 时,假如偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成 Redis 的阻塞。

常用命令

设置 Bitmaps 中某个偏移量的值为 0 或 1。(偏移量可以理解为数组下标,从 0 开始)。

setbit 键名 偏移量 0或1

获取 Bitmaps 中某个偏移量的值(返回 0 或 1)

getbit 键名 指定偏移量的值

统计 Bitmaps 被设置为 1 的 bit 数,可以指定统计区间(注意:统计区间是按照字节来算的,从 0 开始)

bitcount 键名 指定开始字节 指定结束字节

set 与 Bitmaps 对比

当有一组数据只需要存储两种状态(例如:签到、是否访问、男女…),且数据量很大,并且数据量比较密集时,用 Bitmaps 存储效率高于 set;其余情况 set 效率高于 Bitmaps。

例如:假如一个网站的用户巨多,并且要统计是否访问过这个网站,看起来可以使用 Bitmaps 记录每天的访问情况,但如果这个网站日活跃量不高,比如 1000 万 的用户量一天只有10万人访问,那么这个用 id 作为偏移量的存储方式需要存储大量的 0 数据,空间效率其实很低。

HyperLogLog

简介

基数: 给定一组数据,将这组数据去重后剩下的值的个数叫做基数。

HyperLogLog 可以用于统计一组数据的基数的个数,一般可用的场景就是去重。

使用场景:
1、比如统计该网站的访问量,那同个 ip 的多次访问肯定不能算作多次,所以可以使用HyperLogLog 存储
2、统计某篇文章被访问次数,理由同上
3、给定 40 亿个数据(含重复),返回不重复的数据有多少个。

对比其它解决方案
1、如果数据存储在 mysql 表中,可以使用 distinct count 计算个数;
2、如果存在 Redis 中,可以使用 set 存储,然后返回数量;也可以使用 bitmaps 存储(同偏移量只会设置为 1 ),然后返回 1 的数量。

但使用 HyperLogLog 的优点是:当数据量非常大的时候,计算基数的空间总是固定的,并且很小。在 Redis 中,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算出 2^64个不同元素的基数,而 set 则数据量越多消耗的内存就越多。
但缺点是: HyperLogLog 只会根据输入的值来计算基数,本身不会存储元素,所以不能像 set 一样,返回各个元素。(比如上面的场景,如果还要返回 40 亿 个数据去重后的各个元素,那 HyperLogLog 做不到)。

命令

添加一个元素到 HyperLogLog 中

pfadd 键名 值

计算一个或多个键(多个时返回合在一起的结果)的基数值

pfcount 键名1 键名2 ...

将一个或多个键(来源键)合并后存储在另一个键(目标键)中

pfmerge 目标键 来源键1 来源键2...

Geospatial

简介:主要是用于存储地理信息(即经纬度)

常用命令

添加一个地理位置(经度 纬度 地点)
例如(以 china 为键): geoadd china 121.47 31.23 shanghai

geoadd 经度值 纬度值 地点

获取指定地区的坐标值

geopos 键名 地点

获取两个位置之间的直线距离(默认为米)

geodist 键名 地点1 地点2 km/m..

以给定的经纬度为中心,找到半径在指定值内的地点(可用于查找附近的人

georadius 键名 经度值 纬度值 距离 单位

Redis 事务

Redis 事务的简介、执行流程、错误处理

Redis 事务在执行的过程中,不会被其它客户端发送过来的命令所打断。主要作用是串联多个命令防止别的命令插队

Redis 执行事务的流程

三个主要命令两个主要过程
multi 输入这个命令后的命令将会被依次放到一个队列中,但不会执行,直到输入exec命令后,才根据队列依次开始执行各个命令。另外,在输入multi到输入exec之间的过程叫做组队阶段,输入exec到命令执行完毕的过程叫做执行阶段。组队阶段可以使用discard命令用于放弃组队。

组队及执行演示案例:

组队过程中放弃组队演示案例:

组队过程中出现错误的情况:(组队过程中如果某个命令出现了错误,执行时整个的所有队列都不会执行,都会被取消)

执行过程中出现错误的情况:(如果组队的过程没有出错,但是在执行的过程中某个命令出现了错误,那么只有出错的命令不会被执行,其它命令都会执行。)


Redis 事务的冲突问题

悲观锁

每次去拿数据都会上锁,直到操作完成才会释放锁,这个过程别人拿不了数据。

乐观锁

每次去拿数据不上锁,如果有更新数据时发现现在的版本号和数据库中的版本号不一致,则不更新;如果发现版本号一致,则会更新数据,并更新版本号。

Redis 中的锁

Redis 是单线程的,并且单个操作具有原子性,因此执行单个操作的时候并不会有线程安全的问题。但是当我们并发地执行多个事务地时候,我们就需要考虑对操作地数据进行上锁。

Redis 中加锁是通过 watch 命令。在执行multi之前,先执行watch 键名1 键名2 ..对一个或者多个键进行监视,如果在事务执行之前这个(或这些)key 被改动过,那么事务将会被打断。(实际上就是乐观锁,即操作前进行监视,执行时判断这段时间是否被其它命令修改,如果被修改,则打断当前操作。)

实操演示:
1、先打开两个 redis 客户端
客户端1:

客户端2:

2、在客户端1 监视键 balance 并开启事务

3、在客户端2 监视键 balance 并开启事务

4、在客户端1 中对 balance 加 10 并执行
发现可以正常执行成功,因为从监视到执行这个过程中这个值并没有被修改(一直都是 100)

4、在客户端2 中对 balance 加 20 并执行
发现返回空(nil,即空),表示执行失败,并没有执行,因为从监视到执行这个过程中这个值已经被客户端1修改了(从100修改成110,即对应乐观锁中的版本已经不一致的概念,所以放弃当前事务的操作。)

Redis 事务的三特性


参数是否合法(包括非空等)
服务是否开始
操作是否合法(包括当前操作的用户是否有操作的权限、是否重复提交的判断等等)
服务是否结束
服务进行

Redis 超卖问题

Redis 准备好以下数据

如果我们正常写代码,不考虑并发的情况下,是没有问题的。

但在并发情况下,会出现两个问题:
1、连接超时问题 :这是因为并发请求同时到达 Redis,导致 Redis 超时,只需要配置好连接池即可解决。
2、超卖问题,即可能出现库存为负数的情况 :这个时候需要将一系列操作变成一个事务,并在事务开始前对库存加锁(即监视),即可解决。

优化后代码如下:
代码参考自 https://zhangc233.github.io/2021/05/02/Redis/#%E8%B6%85%E5%8D%96%E9%97%AE%E9%A2%98

public class SecKill_redis 

	public static void main(String[] args) 
		Jedis jedis =new Jedis("192.168.44.168",6379);
		System.out.println(jedis.ping());
		jedis.close();
	

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException 
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) 
			return false;
		

		//2 连接redis
		//Jedis jedis = new Jedis("192.168.44.168",6379);
		//通过连接池得到jedis对象
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPoolInstance.getResource();

		//3 拼接key
		// 3.1 库存key
		String kcKey = "sk:"+prodid+":qt";
		// 3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";

		//监视库存
		jedis.watch(kcKey);

		//4 获取库存,如果库存null,秒杀还没有开始
		String kc = jedis.get(kcKey);
		if(kc == null) 
			System.out.println("秒杀还没有开始,请等待");
			jedis.close();
			return false;
		

		// 5 判断用户是否重复秒杀操作
		if(jedis.sismember(userKey, uid)) 
			System.out.println("已经秒杀成功了,不能重复秒杀");
			jedis.close();
			return false;
		

		//6 判断如果商品数量,库存数量小于1,秒杀结束
		if(Integer.parseInt(kc)<=0) 
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		

		//7 秒杀过程
		//使用事务
		Transaction multi = jedis.multi();

		//组队操作
		multi.decr(kcKey);
		multi.sadd(userKey,uid);

		//执行
		List<Object> results = multi.exec();

		if(results == null || results.size()==0) 
			System.out.println("秒杀失败了....");
			jedis.close();
			return false;
		

		//7.1 库存-1
		//jedis.decr(kcKey);
		//7.2 把秒杀成功用户添加清单里面
		//jedis.sadd(userKey,uid);

		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	

以上示例解决了 Redis 事务在并发情况下的"超卖"问题,但实际上上面的代码还会出现一个问题:当并发数比较高时,会出现滞销现象。
滞销问题出现原因:Redis 的锁是乐观锁,是通过版本号来判断是否可以操作 key,那么假如现在有 2000 个并发请求都读取到了版本为 v1.0 的 key1,其中有一个请求修改了 key1,并更新版本为 v1.1 ,这时候其它的请求想要修改,发现版本不一致(v1.1 != v1.0),那么这些事务都会被取消,最后,2000 个请求只是让库存减少了 1 ,但库存明明有很多,出现了滞销。

解决方法:我们知道,问题的根本是 Redis 使用的是乐观锁的机制,那么只能通过 lua 脚本来实现。

Redis 持久化操作

Redis 的特点之一就是支持持久化。Redis 持久化的方式主要有两种,分别是 RDB 和 AOF。Redis 的数据是存在内存中的,持久化技术可以让内存中的数据存到硬盘中。

RDB

什么是RDB

在指定的时间间隔内将内存的数据集快照写入磁盘。

时间间隔:例如指定每隔 10 分钟持久化一次,就可能在10:0010:10进行两次持久化。
数据集快照:以当前时间点为界,在这之前的数据。

RDB 持久化的过程

Redis 会单独创建一个子进程(fork)来进行持久化,会将数据先写入到一个临时文件中,等持久化过程结束了,再用这

以上是关于Redis 6 知识点总结的主要内容,如果未能解决你的问题,请参考以下文章

JS排序算法总结:基数排序

Redis缓存知识点

算法知识详解基数排序算法

Redis 知识点扫盲

mysql基本知识点梳理和查询优化

进制转换内容总结

(c)2006-2024 SYSTEM All Rights Reserved IT常识