Redis - NoSQL 和 Jedis 入门

本文介绍了 Redis 的基础知识,包括互联网架构演变、Redis 的优势和与其他 NoSQL 数据库的对比,以及 CAP 原理。详细讲解了 Redis 的安装、配置、数据类型和持久化策略。同时,提到了 Redis 的事务处理、发布订阅功能,以及主从复制和哨兵模式。文章最后探讨了 Jedis 作为 Java 客户端在 Redis 高并发场景下的分布式锁实现。
摘要由CSDN通过智能技术生成

概述

互联网架构的演变历程

第 1 阶段:

  • 数据访问量不大,简单的架构即可搞定。
  • 适合小型项目。
app -> dao -> mysql

第 2 阶段:

  • 数据访问量大,使用缓存技术来缓解数据库的压力。
  • 不同的业务访问不同的数据库。
  • 适合中型项目。
app -> dao -> cache -> [mysql1, mysql2, mysql3]

第 3 阶段:

  • 主从读写分离。
  • 之前的缓存确实能够缓解数据库的压力,但是写和读都集中在一个数据库上,压力又了。
  • 一个数据库负责写,一个数据库负责读;分工合作。
  • 让 Master(主数据库)来响应事务性(增删改)操作,让 Slave(从数据库)来响应非事务性(查询)操作,然后再采用主从复制来把 Master 上的事务性操作同步到 Slave 数据库中。
  • MySQL 的 Master / Slave 就是网站的标配。
  • 适合大型项目。
app -> dao -> cache -> 主库 -> [从库1, 从库2]

第 4 阶段:

  • 在 MySQL 的主从复制,读写分离的基础上,MySQL 的主库开始出现瓶颈。
  • 由于 MyISAM 使用表锁,所以并发性能特别差。
  • 分库分表开始流行,MySQL 也提出了表分区,虽然不稳定,但有了希望。
  • 使用 MySQL 集群。
  • 适合超大型项目。
app -> dao -> cache -> {[主库 -> (从库1, 从库2)], [主库 -> (从库1, 从库2)]}
Redis 入门介绍

互联网需求的 3 高:高并发,高可扩,高性能。

Redis 是一种运行速度很快,并发性能很强,并且运行在内存上的 NoSQL(Not only SQL)数据库。

NoSQL 非关系型数据库和传统 RDBMS 关系型数据库相比的优势:

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

RDBMS

  • 高度组织化结构化数据
  • 结构化查询语言 SQL
  • 数据和关系都存储在单独的表中
  • 数据操纵语言,数据定义语言
  • 严格的一致性
  • 基础事务

NoSQL

  • 代表着不仅仅是 SQL
  • 没有声明性查询语言
  • 没有预定义的模式
  • 键值对存储,列存储,文档存储,图形数据库
  • 最终一致性,而非 ACID 属性
  • 非结构化和不可预知的数据
  • CAP 定理
  • 高性能,高可用性和可伸缩性

Redis 的常用使用场景:

  • 缓存,是 Redis 当今最为人熟知的使用场景;在提升服务器性能方面非常有效;一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大,而放在 Redis 中,因为 Redis 是放在内存中,可以很高效的访问。

  • 排行榜,在使用传统的关系型数据库(MySQL、Oracle 等)来做这个事儿,非常的麻烦,而利用 Redis 的 SortSet(有序集合)数据结构能够简单的搞定。

  • 计算器 / 限速器,利用 Redis 中原子性的自增操作,可以统计类似用户点赞数、用户访问数等,这类操作如果用 MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个 API 的频率,常用的有抢购时防止用户疯狂点击带来不必要的压力。

  • 好友关系,利用集合的一些命令,比如求交集、并集、差集等;可以方便搞定一些共同好
    友、共同爱好之类的功能。

  • 简单消息队列,除了 Redis 自身的发布 / 订阅模式,也可以利用 List 来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的 DB 压力,完全可以用 List 来完成异步解耦。

  • Session 共享,以 JSP 为例,默认 Session 是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用 Redis 保存 Session 后,无论用户落在那台机器上都能够获取到对应的 Session 信息。

Redis / Memcache / MongoDB 对比

Redis / Memcache / MongoDB 都是 NoSQL 数据库。

Redis 和 Memcache
  • Redis 和 Memcache 都是内存数据库;不过 Memcache 还可用于缓存其他东西,例如图片、视频等等。
  • Memcache 数据结构单一 key / value;Redis 更丰富一些,还提供 list,set, hash 等数据结构的存储,有效的减少网络 IO 的次数。
  • 虚拟内存 – Redis 当物理内存用完时,可以将一些很久没用到的 value 交换到磁盘。
  • 存储数据安全 – Memcache 挂掉后,数据没了(没有持久化机制);Redis 可以定期保存到磁盘(持久化)。
  • 灾难恢复 – Memcache 挂掉后,数据不可恢复;Redis 数据丢失后可以通过 RBD(将 Redis 在内存中的数据库记录定时 dump 到磁盘上进行持久化)或 AOF(将 Redis 的操作日志以追加的方式写入文件)恢复。
Redis 和 MongoDB
  • Redis 和 MongoDB 并不是竞争关系,更多的是一种协作共存的关系。
  • MongoDB 是一个基于分布式文件存储的数据库,是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
  • MongoDB 本质上还是硬盘数据库,在复杂查询时仍然会有大量的资源消耗,而且在处理复杂逻辑时仍然要不可避免地进行多次查询。
  • MongoDB 需要 Redis 或 Memcache 这样的内存数据库来作为中间层进行缓存和加速。
  • 比如在某些复杂页面的场景中,整个页面的内容如果都从 MongoDB 中查询,可能要几十个查询语句,耗时很长;如果需求允许,则可以把整个页面的对象缓存至 Redis 中,定期更新;这样 MongoDB 和 Redis 就能很好地协作起来。
分布式数据库 CAP 原理
CAP 简介

传统的关系型数据库事务具备 ACID:

  • Atomicity 原子性
  • Consistency 一致性
  • Isolation 独立性
  • Durability 持久性

分布式数据库的 CAP:

  • Consistency - 强一致性

All nodes see the same data at the same time,更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性;一致性的问题在并发系统中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题;从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

  • Availability - 高可用性

可用性指 Reads and writes always succeed,即服务一直可用,而且要是正常的响应时间;好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。

  • Partition Tolerance - 分区容错性

即分布式系统在遇到某节点或网络分区故障时,仍然能够对外提供满足一致性或可用性的服务;分区容错性要求能够让应用,虽然是一个分布式系统,但看上去却是一个可以运转正常的整体;比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。

CAP 理论

CAP 理论提出就是针对分布式数据库环境的,所以,P 这个属性必须容忍它的存在,而且是必须具备的。

因为 P 是必须的,那么需要选择的就是 A 和 C。

在分布式环境下,为了保证系统可用性,通常都采取了复制的方式,避免一个节点损坏,导致系统不可用。那么就出现了每个节点上的数据出现了很多个副本的情况,而数据从一个节点复制到另外的节点时需要时间和要求网络畅通的,所以,当 P 发生时,也就是无法向某个节点复制数据时,这时候你有两个选择:

  • 选择可用性 A,此时,那个失去联系的节点依然可以向系统提供服务,不过它的数据就不能保证是同步的了(失去了 C 属性)。

  • 选择一致性 C,为了保证数据库的一致性,必须等待失去联系的节点恢复过来,在这个过程中,那个节点是不允许对外提供服务的,这时候系统处于不可用状态(失去了 A 属性)。

最常见的例子是读写分离,某个节点负责写入数据,然后将数据同步到其它节点,其它节点提供读取的服务,当两个节点出现通信问题时,就面临着选择 A - 继续提供服务,但是数据不保证准确,C - 用户处于等待状态,一直等到数据同步完成。

CAP 总结

分区是常态,不可避免,三者不可共存。

可用性和一致性:

  • 一致性高,可用性低
  • 一致性低,可用性高

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

  • CA - 单点集群(非分布式),满足一致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

下载与安装

下载

Redis:http://www.redis.net.cn

图形工具:https://redisdesktop.com/download

安装

虽然可以在安装在 windows 操作系统,但是官方不推荐,所以安装在 Linux 系统中。

1)上传 tar.gz 包 到 /opt 目录下,并解压:

tar -zxvf redis-5.0.4.tar.gz

2)安装 gcc(必须有网络):

yum -y install gcc

忘记是否安装过,可以使用 gcc -v 命令查看 gcc 版本,如果没有安装过,会提示命令不存在。

3)进入 Redis 目录,进行编译:

make

4)编译之后,开始安装:

make install
安装后的操作
后台运行方式

Redis 默认不会使用后台运行,如果需要,修改配置文件 daemonize=yes,当后台服务启动的时候,会写成一个进程文件运行。

打开配置文件:

vim /opt/redis-5.0.4/redis.conf

注释掉 bind,关闭保护模式,并修改为后台启动:

...
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
# internet, binding to all the interfaces is dangerous and will expose the
# instance to everybody on the internet. So by default we uncomment the
# following bind directive, that will force Redis to listen only into
# the IPv4 loopback interface address (this means Redis will be able to
# accept connections only from clients running into the same computer it
# is running).
#
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT THE FOLLOWING LINE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# bind 127.0.0.1

...

# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode no

...

# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes

...

以配置文件的方式启动:

cd /usr/local/bin
redis-server /opt/redis-5.0.4/redis.conf
29674:C 02 Oct 2020 02:56:47.338 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
29674:C 02 Oct 2020 02:56:47.338 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=29674, just started
29674:C 02 Oct 2020 02:56:47.338 # Configuration loaded

防火墙开放 Redis 的端口号:

firewall-cmd --zone=public --add-port=6379/tcp --permanent
firewall-cmd --reload
关闭数据库

单实例关闭

redis-cli shutdown

多实例关闭

redis-cli -p 6379 shutdown
常用操作

检测 6379 端口是否在监听

netstat -lntp | grep 6379

检测后台进程是否存在

ps -ef | grep redis
连接 Redis 并测试
redis-cli
ping

Redis 在 linux 支持命令补全(tab)

HelloWorld
# 保存数据
set k1 china   
# 获取数据
get kl 
测试性能

先 ctrl + c,退出 Redis 客户端

redis-benchmark 

执行命令后,命令不会自动停止,需要手动 ctrl+c 停止测试

====== PING_INLINE ======
  100000 requests completed in 2.52 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

62.39% <= 1 milliseconds
99.95% <= 2 milliseconds
99.97% <= 3 milliseconds
100.00% <= 3 milliseconds
39682.54 requests per second

...
默认 16 个数据库
vim /opt/redis-5.0.4/redis.conf
...

# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16

...
127.0.0.1:6379> get k1                 # 查询 k1
"china"
127.0.0.1:6379> select 16              # 切换 16 号数据库
(error) ERR DB index is out of range   # 数据库的下标超出了范围
127.0.0.1:6379> select 15              # 切换 15 号数据库
OK
127.0.0.1:6379[15]> get k1             # 查询 k1
(nil)
127.0.0.1:6379[15]> select 0           # 切换 0 号数据库
OK
127.0.0.1:6379> get k1                 # 查询 k1
"china"
数据库键的数量
dbsize
清空数据库

清空当前库

flushdb

清空所有(16个)库,慎用

flushall
模糊查询(keys)

模糊查询 keys 命令,有三个通配符:*?[]

* - 通配任意多个字符

查询所有的键:

keys *

模糊查询 k 开头,后面随便多少个字符:

keys k*

模糊查询 e 为最后一位,前面随便多少个字符:

keys *e

* 模式,匹配任意多个字符 - 查询包含 k 的键:

keys *k*
? - 通配单个字符

模糊查询 k 字头,并且匹配一个字符:

keys k?

只记得第一个字母是 k,长度是 3

keys k??
[] - 通配括号内的某一个字符

记得其他字母,第二个字母可能是 a 或 e

keys r[ae]dis

exists key - 判断某个 key 是否存在

127.0.0.1:6379[15]> EXISTS k1
(integer) 1
127.0.0.1:6379[15]> EXISTS y1
(integer) 0

move key db - 移动(剪切,粘贴)键到几号库

127.0.0.1:6379[15]> EXISTS x1   # 将 x1 移动到 8 号库
(integer) 1                     # 移动成功
127.0.0.1:6379[15]> MOVE x1 8   # 查看当前库中是否存在 x1
(integer) 1                     # 不存在(因为已经移走了)
127.0.0.1:6379[15]> EXISTS x1   # 切换 8 号库
(integer) 0
127.0.0.1:6379[15]> SELECT 8    # 查看当前库中的所有键
OK
127.0.0.1:6379[8]> KEYS *
1) "x1"

ttl key - 查看键还有多久过期(-1 永不过期,-2 已过期);Time To Live

127.0.0.1:6379[8]> TTL x1
(integer) -1                # 永不过期

expire key 秒 - 为键设置过期时间(生命倒计时)

127.0.0.1:6379[8]> set k1 v1       # 保存 k1
OK
127.0.0.1:6379[8]> TTL k1          # 查看 k1 的过期时间
(integer) -1                       # 永不过期
127.0.0.1:6379[8]> EXPIRE k1 10    # 设置 k1 的过期时间为 10 秒
(integer) 1                        # 设置成功
127.0.0.1:6379[8]> get k1          # 获取 k1
"v1"
127.0.0.1:6379[8]> TTL k1          # 查看 k1 的过期时间
(integer) 1                        # 还有 1 秒过期
127.0.0.1:6379[8]> get k1
(nil)                              # 从内存中销毁了

type key - 查看键的数据类型

127.0.0.1:6379[8]> type k1
string                             # k1 的数据类型是 string 字符串

使用 Redis

五大数据类型

操作文档:http://redisdoc.com/

字符串 String
set / get / del / append / strlen
127.0.0.1:6379[8]> set k1 v1     # 保存数据
OK
127.0.0.1:6379[8]> set k2 v2     # 保存数据
OK
127.0.0.1:6379[8]> keys *
1) "k2"
2) "k1"
127.0.0.1:6379[8]> del k2        # 删除数据 k2
(integer) 1
127.0.0.1:6379[8]> keys *
1) "k1"
127.0.0.1:6379[8]> get k1        # 获取数据 k1
"v1"
127.0.0.1:6379[8]> append k1 abc # 往 k1 的值追加数据 abc
(integer) 5                      # 返回值的长度(字符数量)
127.0.0.1:6379[8]> get k1
"v1abc"
127.0.0.1:6379[8]> strlen k1     # 返回 k1 值的长度(字符数量)
(integer) 5
incr / decr / incrby / decrby

加减操作,操作的必须是数字类型

incr - increment
decr - decrement

127.0.0.1:6379[8]> set k1 1       # 初始化 k1 的值为 1
OK
127.0.0.1:6379[8]> incr k1        # k1 自增 1(相当于 ++)
(integer) 2
127.0.0.1:6379[8]> incr k1
(integer) 3
127.0.0.1:6379[8]> get k1
"3"
127.0.0.1:6379[8]> decr k1        # k1 自减 1(相当于 --)
(integer) 2
127.0.0.1:6379[8]> decr k1
(integer) 1
127.0.0.1:6379[8]> get k1
"1"
127.0.0.1:6379[8]> INCRBY k1 3    # k1 自增 3(相当于 +=3)
(integer) 4
127.0.0.1:6379[8]> GET k1
"4"
127.0.0.1:6379[8]> DECRBY k1 2    # k1 自减 2(相当于 -=2)
(integer) 2
127.0.0.1:6379[8]> get k1
"2"
getrange / setrange

类似 between … and …

range:范围

# 初始化 k1 的值为 abcdef
127.0.0.1:6379[8]> SET k1 abcdef
OK
127.0.0.1:6379[8]> get k1
"abcdef"
# 查询 k1 全部的值
127.0.0.1:6379[8]> getrange k1 0 -1
"abcdef"
# 查询 k1 的值,范围是下标 0 ~ 下标 3(包含 0 和 3,共返回 4 个字符)
127.0.0.1:6379[8]> getrange k1 0 3
"abcd"
# 替换 k1 的值,从下标 1 开始提供为 xxx
127.0.0.1:6379[8]> setrange k1 1 xxx
(integer) 6
127.0.0.1:6379[8]> get k1
"axxxef"
setex / setnx

set with expir:添加数据的同时设置生命周期

# 添加 k1 v1 数据的同时,设置 5 秒的声明周期
127.0.0.1:6379[8]> SETEX k1 5 v1
OK
127.0.0.1:6379[8]> get k1
"v1"
# 已过期,k1 的值 v1 自动销毁
127.0.0.1:6379[8]> get k1
(nil)

set if not exist:添加数据的时候判断是否已经存在,防止已存在的数据被覆盖掉

# k1 不存在,添加成功
127.0.0.1:6379[8]> setnx k1 v1
(integer) 1
# 添加失败,因为 k1 已经存在
127.0.0.1:6379[8]> setnx k1 renda
(integer) 0
mset / mget / msetnx

m:更多

# set 不支持一次添加多条数据
127.0.0.1:6379[8]> set k1 v1 k2 v2
(error) ERR syntax error
# mset 可以一次添加多条数据
127.0.0.1:6379[8]> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379[8]> keys *
1) "k2"
2) "k1"
3) "k3"
# 一次获取多条数据
127.0.0.1:6379[8]> mget k2 k3
1) "v2"
2) "v3"
# 一次添加多条数据时,如果添加的数据中有已经存在的,则失败
127.0.0.1:6379[8]> msetnx k3 v3 k4 v4
(integer) 0
# 一次添加多条数据时,如果添加的数据中都不存在的,则成功
127.0.0.1:6379[8]> msetnx k4 v4 k5 v5
(integer) 1

getset:先 get 后 set

# 因为没有 k6,所以 get 为 null,然后将 k6 的值 v6 添加到数据库
127.0.0.1:6379[8]> getset k6 v6
(nil)
127.0.0.1:6379[8]> keys *
1) "k6"
2) "k2"
3) "k1"
4) "k4"
5) "k5"
6) "k3"
127.0.0.1:6379[8]> get k6
"v6"
# 先获取 k6 的值,然后修改 k6 的值为 vv6
127.0.0.1:6379[8]> getset k6 vv6
"v6"
127.0.0.1:6379[8]> get k6
"vv6"
列表 List
lpush / rpush / lrange

l:left 自左向右添加 (从上往下添加)

r:right 自右向左添加(从下往上添加)

# 从上往下添加
127.0.0.1:6379[1]> lpush list01 1 2 3 4 5
(integer) 5
127.0.0.1:6379[1]> keys *
1) "list01"
# 查询 list01 中的全部数据 0 表示开始,-1 表示结尾
127.0.0.1:6379[1]> lrange list01 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
# 从下往上添加
127.0.0.1:6379[1]> rpush list02 1 2 3 4 5
(integer) 5
127.0.0.1:6379[1]> lrange list02 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
lpop / rpop

移除第一个元素(上左下右)

# 从左(上)边移除第一个元素
127.0.0.1:6379[1]> LPOP list02
"1"
# 从右(下)边移除第一个元素
127.0.0.1:6379[1]> RPOP list02
"5"
lindex

根据下标查询元素(从左向右,自上而下)

127.0.0.1:6379[1]> lrange list01 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
# 从上到下数,下标为 2 的值
127.0.0.1:6379[1]> lindex list01 2
"3"
# 从上到下数,下标为 1 的值
127.0.0.1:6379[1]> lindex list01 1
"4"
llen
127.0.0.1:6379[1]> llen list01
(integer) 5
lrem

删除 n 个 value

127.0.0.1:6379[1]> lpush list01 1 2 2 3 3 3 4 4 4 4
(integer) 10
# 从 list01 中移除 2 个 3
127.0.0.1:6379[1]> lrem list01 2 3
(integer) 2
127.0.0.1:6379[1]> lrange list01 0 -1
 1) "4"
 2) "4"
 3) "4"
 4) "4"
 5) "3"
 6) "2"
 7) "2"
 8) "1"
ltrim

截取指定范围的值,别的全扔掉

ltrim key begindex endindex

127.0.0.1:6379[1]> lpush list01 1 2 3 4 5 6 7 8 9
(integer) 9
127.0.0.1:6379[1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值