Redis学习
在学习Redis前需要了解NoSQL,因为Redis属于NoSQL中的键值(Key-Value)存储数据库。
NoSQL有四大类分别是:
# 1.说明:
- 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。
# 2.特点
- Key/value模型对于IT系统来说的优势在于简单、易部署。
- 但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。
# 3.相关产品
- Tokyo Cabinet/Tyrant,
- Redis 基于内存的 运行软件--->磁盘--->内存中
- SSDB 基于磁盘的 直接与磁盘做交互--> IO
- Voldemort
- Oracle BDB
# 1.说明
- 这部分数据库通常是用来应对分布式存储的海量数据。
# 2.特点
- 键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。列簇
- rowkey
# 3.相关产品
- Cassandra、HBase、ClickHouse等.
# 1.说明
- 文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可 以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高
{'id':1001,'name':xiaohu}
{'id':1001,'name':'xiaohu2,'address':'anhuihefei','likes':['play','eat'],'study':{'yuyan':java,'ruanjian':'mysql'}}
文档数据库对于单条数据来说,他的事务支持并没有那么强大
目前的mongodb5,支持了单条数据的事务,但是多条不行
# 2.特点
- 以文档形式存储
# 3.相关产品
- MongoDB、CouchDB、 MongoDb(4.x). 国内也有文档型数据库SequoiaDB,已经开源。
# 1.说明
- 图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。
# 2.特点
- NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API。
# 3.相关产品
- Neo4J、InfoGrid、 Infinite Graph、OSS
-
-
Redis支持丰富的数据类型 string,list,set,sorted set 指的是键值对中的值的类型
-
Redis支持持久化 持久化:将数据由内存放入磁盘,主要有快照(Snapshot)和AOF (Append Only File) 只追加日志文件
-
# 0.准备环境
- vmware 12.x+
- centos7.x+
# 1.下载redis源码包
- https://redis.io/

# 2.下载完整源码包
- redis-7.0.0.tar.gz

# 3.将下载redis资料包上传到Linux中
# 4.解压缩文件
[root@localhost ~]# tar -zxvf redis-7.0.0.tar.gz
[root@localhost ~]# ll

redis底层是由C语言编写的
# 5.安装gcc
- yum install -y gcc
# 6.进入解压缩目录执行如下命令
# 注意。一定要在Makefile文件的同目录下使用下面的命令对redis做编译(切记:不要make test)
- yum install -y tcl
- make MALLOC=libc
# 7.编译完成后执行如下命令
- make install PREFIX=/usr/local/soft/redis
# 8.进入/usr/redis目录启动redis服务
- ./redis-server

# 9.Redis服务端口默认是 6379
ps aux|grep redis
# 10.进入bin目录执行客户端连接操作
- ./redis-cli -h localhost –p 6379
# 如果是在一台机器上,可以省略后面的
- ./redis-cli

# 11.连接成功出现上面界面连接成功
注意事项
1、redis服务默认的启动端口号是6379
2、如果想要启动多个redis服务,就得修改端口号
如何修改端口号?
我们猜测,它自身有一个默认的端口号,就应该有一个地方可以去配置这个端口号,而这个地方应该是一个配置文件,但是呢,我们安装后就只有一个bin目录,并没有看到其他的文件,所以我们去源码(解压)目录下去找,找到一个叫做redis.conf的文件,这里面有地方进行修改端口号,这个文件就是redis启动的时候需要读取配置的文件
注意:不要直接在源码目录下修改该文件,请先将redis.conf文件复制到外面某一个目录
第二步,可以修改端口号 port 7000
第三步,启动服务
3、即使你改过了配置文件,但是默认的启动服务方式,依旧是读取源码目录中的redis.conf文件,要想在启动的时候读取的是我们自己修改后的配置文件,就应该去修改启动服务命令的格式:redis-server 配置文件的路径
比如:redis-server ./redis.conf
4、客户端启动的时候,默认是与6379端口号的redis服务连接,所以当自己指定端口号启动的时候,客户端的连接也需要发生变化
redis-cli -h 服务器的ip -p 端口号
5、redis数据库,默认会有16个数据库,这16个数据库的名字是按照序号从0开始编号,一直到15,默认客户端一开始进入使用的是0号库
如何切换库呢?select dbid
库与库之间键能否重名?
set name xiaohu
get name
库与库之间的数据是相对隔离的,不同的库可以定义不同的键
数据库操作指令
# 1.Redis中库说明
- 使用redis的默认配置器动redis服务后,默认会存在16个库,编号从0-15 配置文件中有个database相关的
- 可以使用select 库的编号 来选择一个redis的库
# 2.Redis中清空库的指令
- 清空当前的库 FLUSHDB
- 清空全部的库 FLUSHALL
# 3.redis客户端显示中文
- ./redis-cli -p 7000 --raw
操作key相关指令(分别演示案例)
# 1.DEL指令
- 语法 : DEL key [key ...]
- 作用 : 删除给定的一个或多个key 。不存在的key 会被忽略。多个key之间使用空格隔开
- 可用版本: >= 1.0.0
- 返回值: 被删除key 的数量。
# 2.EXISTS指令
- 语法: EXISTS key
- 作用: 检查给定key 是否存在。多个key之间使用空格隔开,只要有一个key存在,返回值就是1 新版本中会提示几个键存在
- 可用版本: >= 1.0.0
- 返回值: 若key 存在,返回1 ,否则返回0。
# 3.EXPIRE
- 语法: EXPIRE key seconds
- 作用: 为给定key 设置生存时间,当key 过期时(生存时间为0 ),它会被自动删除。
- 可用版本: >= 1.0.0
- 时间复杂度: O(1)
- 返回值:设置成功返回1 。
# 4.KEYS
- 语法 : KEYS pattern
- 作用 : 查找所有符合给定模式pattern 的key 。
- 语法:
KEYS * 匹配数据库中所有key 。
KEYS h?llo 匹配hello ,hallo 和hxllo 等。
KEYS h*llo 匹配hllo 和heeeeello 等。
KEYS h[ae]llo 匹配hello 和hallo ,但不匹配hillo 。特殊符号用 "\" 隔开
- 可用版本: >= 1.0.0
- 返回值: 符合给定模式的key 列表。
# 5.MOVE
- 语法 : MOVE key db (move name 1----将name键移动到1号库)
- 作用 : 将当前数据库的key 移动到给定的数据库db 当中。
- 可用版本: >= 1.0.0
- 返回值: 移动成功返回1 ,失败则返回0 。
# 6.PEXPIRE
- 语法 : PEXPIRE key milliseconds
- 作用 : 这个命令和EXPIRE 命令的作用类似,但是它以毫秒为单位设置key 的生存时间,而不像EXPIRE 命令那样,以秒为单位。
- 可用版本: >= 2.6.0
- 时间复杂度: O(1)
- 返回值:设置成功,返回1 key 不存在或设置失败,返回0
# 7.PEXPIREAT
- 语法 : PEXPIREAT key milliseconds-timestamp
- 作用 : 这个命令和EXPIREAT 命令类似,但它以毫秒为单位设置key 的过期unix 时间戳,而不是像EXPIREAT那样,以秒为单位。
- 可用版本: >= 2.6.0
- 返回值:如果生存时间设置成功,返回1 。当key 不存在或没办法设置生存时间时,返回0 。(查看EXPIRE 命令获取更多信息)
# 8.TTL
- 语法 : TTL key
- 作用 : 以秒为单位,返回给定key 的剩余生存时间(TTL, time to live)。
- 可用版本: >= 1.0.0
- 返回值:
当key 不存在时,返回-2 。
当key 存在但没有设置剩余生存时间时,返回-1 。
否则,以秒为单位,返回key 的剩余生存时间。
- Note : 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。
# 9.PTTL
- 语法 : PTTL key
- 作用 : 这个命令类似于TTL 命令,但它以毫秒为单位返回key 的剩余生存时间,而不是像TTL 命令那样,以秒为单位。
- 可用版本: >= 2.6.0
- 返回值: 当key 不存在时,返回-2 。当key 存在但没有设置剩余生存时间时,返回-1 。
- 否则,以毫秒为单位,返回key 的剩余生存时间。
- 注意 : 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。
# 10.RANDOMKEY
- 语法 : RANDOMKEY
- 作用 : 从当前数据库中随机返回(不删除) 一个key 。
- 可用版本: >= 1.0.0
- 返回值:当数据库不为空时,返回一个key 。当数据库为空时,返回nil 。
# 11.RENAME
- 语法 : RENAME key newkey
- 作用 : 将key 改名为newkey 。当key 和newkey 相同,或者key 不存在时,返回一个错误。当newkey 已经存在时,RENAME 命令将覆盖旧值。
- 可用版本: >= 1.0.0
- 返回值: 改名成功时提示OK ,失败时候返回一个错误。
# 12.TYPE
- 语法 : TYPE key
- 作用 : 返回key 所储存的值的类型。
- 可用版本: >= 1.0.0
- 返回值:
none (key 不存在)
string (字符串)
list (列表)
set (集合)
zset (有序集)
hash (哈希表)
String类型
1. 内存存储模型

2. 常用操作命令
| 命令 | 说明 |
|---|---|
| set | 设置一个key/value |
| get | 根据key获得对应的value |
| mset | 一次设置多个key value |
| mget | 一次获得多个key的value |
| getset | 获得原始key的值,同时设置新值 |
| strlen | 获得对应key存储value的长度 |
| append | 为对应key的value追加内容 |
| getrange 索引0开始 | 截取value的内容 到末尾-1 |
| setex | 设置一个key存活的有效期(秒) |
| psetex | 设置一个key存活的有效期(毫秒) |
| setnx | 存在不做任何操作,不存在添加 |
| msetnx原子操作(只要有一个存在不做任何操作) | 可以同时设置多个key,只有有一个存在都不保存 |
| decr | 进行数值类型的-1操作 |
| decrby | 根据提供的数据进行减法操作 |
| Incr | 进行数值类型的+1操作 |
| incrby | 根据提供的数据进行加法操作 |
| Incrbyfloat | 根据提供的数据加入浮点数(不是四舍五入) |
8.4 List类型
list 列表 相当于java中list 集合 特点 元素有序 且 可以重复,key还是一个字符串,值是一个list
1.内存存储模型

2.常用操作指令
| 命令 | 说明 |
|---|---|
| lpush | 将某个值加入到一个key列表头部 lpush list xiaohu xiaohei xiaoming 当列表不存在的时候会进行创建 |
| lpushx | 同lpush,但是必须要保证这个key存在 必须在列表进行存在的情况下从左插入 |
| rpush | 将某个值加入到一个key列表末尾 |
| rpushx | 同rpush,但是必须要保证这个key存在 |
| lpop | 返回和移除列表左边的第一个元素 |
| rpop | 返回和移除列表右边的第一个元素 |
| lrange | 获取某一个下标区间内的元素 lrange list 0 -1 |
| llen | 获取列表元素个数 |
| lset | 设置某一个指定索引的值(索引必须存在) |
| lindex | 获取某一个指定索引位置的元素 |
| lrem | 删除重复元素 |
| ltrim | 保留列表中特定区间内的元素 |
| linsert | 在某一个元素之前,之后插入新元素 |
8.5 Set类型
特点: Set类型 Set集合 元素无序 不可以重复
1.内存存储模型

2.常用命令
| 命令 | 说明 |
|---|---|
| sadd | 为集合添加元素 |
| smembers | 显示集合中所有元素 无序 |
| scard | 返回集合中元素的个数 |
| spop | 随机返回一个元素 并将元素在集合中删除 |
| smove | 从一个集合中向另一个集合移动元素 必须是同一种类型 |
| srem | 从集合中删除一个元素 |
| sismember | 判断一个集合中是否含有这个元素 |
| srandmember | 随机返回元素 后面可以加数字 表示每次返回的个数 |
| sdiff | 去掉第一个集合中其它集合含有的相同元素 |
| sinter | 求交集 |
| sunion | 求和集 |
8.6 ZSet类型
特点: 可排序的set集合 排序 不可重复
ZSET 官方 可排序SET sortSet
1.内存模型

2.常用命令
| 命令 | 说明 |
|---|---|
| zadd | 添加一个有序集合元素 zadd zset 2 xiaohu 3 xiaohu2 |
| zcard | 返回集合的元素个数 |
| zrange 升序 zrevrange 降序 | 返回一个范围内的元素 如果想看看分数 withscores |
| zrangebyscore | 按照分数查找一个范围内的元素 zrangebyscore zset 0 20 withscores limit 0 2 |
| zrank | 返回排名 |
| zrevrank | 倒序排名 |
| zscore | 显示某一个元素的分数 |
| zrem | 移除某一个元素 |
| zincrby | 给某个特定元素加分 |
8.7 hash类型
特点: value 是一个map结构 存在key value key 无序的
Map<String,Map<String,value>> map
举例:map name zhangsan
1.内存模型

2.常用命令
| 命令 | 说明 |
|---|---|
| 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加入浮点值 |
第一天说完上面知识点后,介绍可视化工具的安装使用(引入修改配置文件,使得远程连接)mac电脑的叫做medis https://getmedis.com/
下载官网:https://redisdesktop.com/download
注意问题:
1、如果还是连接不上,查看防火墙
持久化机制
client redis[内存] -----> 内存数据- 数据持久化-->磁盘
Redis官方提供了两种不同的持久化方法来将内存的数据存储到硬盘里面分别是:
-
-
AOF (Append Only File) 只追加日志文件
一、快照(Snapshot)
1.特点
这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis的默认开启持久化方式,保存的文件是以.rdb形式结尾的文件因此这种方式也称之为RDB方式。
官方说法叫快照持久化

2.快照生成方式
-
客户端方式: BGSAVE 和 SAVE指令
-
服务器配置自动触发
# 1.客户端方式之BGSAVE
- a.客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端的BGSAVE命令时,redis会调用fork¹来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。
`名词解释: fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类似于unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务`

# 2.客户端方式之SAVE
- b.客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前将不再响应任何其他的命令

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

# 4.服务器接收客户端shutdown指令
- 当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
3.配置生成快照名称和位置
#1.修改生成快照名称
- dbfilename dump.rdb
# 2.修改生成位置
- dir ./

演示断电操作,这个持久化并不是太好,可能会造成数据丢失的问题(刚刚做完一次快照,又来了写数据请求断电)
二、 AOF 只追加日志文件
1.特点
这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集.

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


3.日志追加频率
# 1.always 【谨慎使用】
- 说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
- 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
- 注意: 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s;
- 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的`写入放大`问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
# 2.everysec 【推荐默认】
- 说明: 每秒执行一次同步显式的将多个写命令同步到磁盘
- 解释: 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
# 3.no 【不推荐】
- 说明: 由操作系统决定何时同步
- 解释:最后使用no选项,将完全由操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,甚至丢失全部数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
4.修改同步频率
# 1.修改日志同步频率
- 修改appendfsync everysec|always|no 指定

1.3 AOF文件的重写(面试必问)
1. AOF带来的问题
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。
2. AOF重写
用来在一定程度上减小AOF文件的体积,并且还能保证数据不丢失
3. 触发重写方式
# 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设置为更大

4. 重写原理
从 Redis 7.0.0 开始,Redis 使用了多部分 AOF 机制。也就是将原来的单个AOF文件拆分为基础文件(最多一个)和增量文件(可能不止一个)。
从 Redis 7.0.0 开始,在调度 AOF 重写时,Redis 父进程会打开一个新的增量 AOF 文件继续写入。子进程执行重写逻辑并生成新的基础 AOF。Redis 将使用一个临时清单文件来跟踪新生成的基础文件和增量文件。当它们准备好后,Redis 会执行原子替换操作,使这个临时清单文件生效。为了避免在 AOF 重写重复失败和重试的情况下创建大量增量文件的问题,Redis 引入了 AOF 重写限制机制,以确保失败的 AOF 重写以越来越慢的速度重试。
日志重写
注意:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。
# 重写流程 - 1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 - 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。 - 3. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。 - 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
Redis7.0.0之前:

Redis7.0.0之后:

1.4 持久化总结
两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。
无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。
2、位图
零存零取,整存零取,整存整存
1.bitmap介绍
位图不是真正的数据类型,它是定义在字符串类型中,一个字符串类型的值最多能存储512M字节的内容
位上限:2^(9(512)+10(1024)+10(1024)+3(8b=1B))=2^32b
2.setbit 设置某一位上的值
语法:SETBIT key offset value (offset位偏移量,从0开始)
127.0.0.1:7000> flushall OK 127.0.0.1:7000> setbit k1 1 1 0 127.0.0.1:7000> get k1 @ 127.0.0.1:7000> setbit k1 7 1 0 127.0.0.1:7000> get k1 A 127.0.0.1:7000> setbit k1 7 2 ERR bit is not an integer or out of range

127.0.0.1:7000> setbit k1 9 1 0 127.0.0.1:7000> get k1 A@


3.getbit 获取某一位上的值
语法:GETBIT key offset
127.0.0.1:7000> getbit k1 7 1 127.0.0.1:7000> getbit k1 8 0 127.0.0.1:7000> getbit k1 1 1

4.bitpos返回指定值0或者1在指定区间上首次出现的下标
语法:BITPOS key bit [start] [end](字节索引,0表示第一个字节)
summary: Find first bit set or clear in a string since: 2.8.7 group: string
不指定查找范围,表示从全部内容中查找:BITPOS key bit
127.0.0.1:7000> keys * k1 127.0.0.1:7000> bitpos k1 1 1 127.0.0.1:7000> setbit k1 1 0 1 127.0.0.1:7000> bitpos k1 1 7 127.0.0.1:7000> setbit k1 7 0 1 127.0.0.1:7000> bitpos k1 1 9

指定查找范围:
BITPOS key bit start :从start+1个字节开始查找,直到尾部 BITPOS key bit start end:从start+1字节开始到end+1字节之间查找
然后将数据还原:
127.0.0.1:7000> setbit k1 1 1 0 127.0.0.1:7000> setbit k1 7 1 0
查找演示:
127.0.0.1:7000> bitpos k1 1 0 0 1 #在第一个字节中查找1首次出现的下标 127.0.0.1:7000> bitpos k1 1 0 1 #从第一个字节到值得最后一个字节查找1首次出现的下标 127.0.0.1:7000> setbit k1 1 0 1 #将指定下标的值改为0 127.0.0.1:7000> bitpos k1 1 0 0 7 # 127.0.0.1:7000> bitpos k1 1 0 7 127.0.0.1:7000> setbit k1 7 0 1 127.0.0.1:7000> bitpos k1 1 0 0 -1 #在第一个字节中没有找到1,则返回-1 127.0.0.1:7000> bitpos k1 1 0 9 #从第一个字节到值得最后一个字节查找 127.0.0.1:7000> bitpos k1 1 0 1 9 #在第1和第2个字节总找1首次出现的位置 127.0.0.1:7000> bitpos k1 1 0 2 9 #在第1到第3个字节查找1首次出现的位置,但数据总共2(小于end对应的3)个字节,不会抛错。
5.bitop位操作
语法:BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings since: 2.6.0 group: string
对一个或多个保存二进制位的字符串 key 进行位操作,并将结果保存到 destkey 上。operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种
BITOP AND destkey key [key ...] ,对一个或多个 key 求逻与,并将结果保存到 destkey
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入,当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0,空的 key 也被看作是包含 0 的字符串序列
BITOP AND destkey key [key ...]演示:
127.0.0.1:7000> flushall OK 127.0.0.1:7000> keys * (empty list or set) 127.0.0.1:7000> setbit k1 1 1 (integer) 0 127.0.0.1:7000> setbit k2 7 1 (integer) 0 127.0.0.1:7000> bitop and k3 k1 k2 (integer) 1 127.0.0.1:7000> get k3 "\x00"

BITOP OR destkey key [key ...]演示
127.0.0.1:7000> bitop or k4 k1 k2 (integer) 1 127.0.0.1:7000> get k4 "A"

BITOP XOR destkey key [key ...]
127.0.0.1:7000> bitop xor k5 k1 k2 (integer) 1 127.0.0.1:7000> get k5 "A"

127.0.0.1:7000> bitop not k6 k1 (integer) 1 127.0.0.1:7000> get k6 "\xbf"

6.bitcount
统计指定位区间上值为1的个数
-
BITCOUNT key [start] [end] start end 字节的索引 正方向
从左向右从0开始,注意官方start、end是位,测试后是字节
127.0.0.1:7000> get k1 "@" 127.0.0.1:7000> bitcount k1 (integer) 1 127.0.0.1:7000> setbit k1 7 1 (integer) 0 127.0.0.1:7000> bitcount k1 (integer) 2 127.0.0.1:7000> setbit k1 9 1 (integer) 0 127.0.0.1:7000> bitcount k1 (integer) 3 #统计全部的1的总数 127.0.0.1:7000> bitcount k1 0 0 (integer) 2 #统计第一个字节中1出现的总数 127.0.0.1:7000> bitcount k1 0 1 (integer) 3 #统计第0+1到第1+1字节中1出现的总数
-
BITCOUNT key [start] [end] start end 字节的索引 负方向
从右向左从-1开始,注意官方start、end是位,测试后是字节
127.0.0.1:7000>BITCOUNT k1 0 -1 #等同于BITCOUNT k1 (integer) 3
最常用的就是 BITCOUNT k1
7.Redis的二进制位
127.0.0.1:7000> set k7 ab OK 127.0.0.1:7000> get k7 "ab" 127.0.0.1:7000> bitcount k7 (integer) 6 127.0.0.1:7000> bitcount k7 0 0 (integer) 3 127.0.0.1:7000> bitcount k7 1 1 (integer) 3

127.0.0.1:7000> set k8 中 OK 127.0.0.1:7000> bitcount k8 (integer) 13 127.0.0.1:7000> get k8 "\xe4\xb8\xad"

8.Bitmap应用场景
网站用户签到的天数统计
用户ID为key,天作为offset,上线置为1 366> 000000000000000
366 /8=46Byte ID为18的用户,今年的第1天签到、第30天签到
127.0.0.1:7000[2]> setbit u18 1 1 (integer) 0 127.0.0.1:7000[2]> setbit u18 30 1 (integer) 0 127.0.0.1:7000[2]> bitcount u18 #统计id为18的用户签到总次数 (integer) 2 127.0.0.1:7000[2]> keys u* 1) "u18"
按天统计网站活跃用户
天作为key,用户ID为offset,上线置为1
求一段时间内活跃用户数 5000 0000 / 8366= 6.3MB=366 (五千万活跃用户1年才产生2GB左右的数据)
127.0.0.1:7000>SETBIT 20190601 5 1 #0000 0100 127.0.0.1:7000>SETBIT 20190602 7 1 #0000 0001 127.0.0.1:7000>SETBIT 20190603 7 1 #0000 0001 求6月1日到6月10日的活跃用户数 127.0.0.1:7000>BITOP OR users 20190601 20190602 20190603 ... 20190610 127.0.0.1:7000>BITCOUNT users #目标key为users 结果为2
用户在线状态、在线人数统计
127.0.0.1:7000> SETBIT online 5 1 #0000 0100 上线为1 (integer) 0 127.0.0.1:7000> SETBIT online 7 1 #0000 0101 (integer) 0 127.0.0.1:7000> bitcount online #当前在线人数 (integer) 2 127.0.0.1:7000> SETBIT online 7 0 (integer) 1 127.0.0.1:7000> bitcount online #当前在线人数 (integer) 1
3. java操作Redis
10.1 环境准备
1. 引入依赖
<!--引入jedis连接依赖--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.2</version> </dependency>
2.创建jedis对象
public static void main(String[] args) {
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.40.110", 7000);//1.redis服务必须关闭防火墙 2.redis服务必须开启远程连接
jedis.select(0);//选择操作的库默认0号库
//2.执行相关操作
//....
//3.释放资源
jedis.close();
}
10.2 操作key相关API
//测试与key相关的操作
@Test
public void testKey(){
jedis.set("name","xiaohu");
jedis.set("age","18");
jedis.set("age1","19");
jedis.set("age2","20");
jedis.set("age3","21");
//删除一个键
long l = jedis.del("age1");
if(l==1){
System.out.println("成功删除age1键");
}
//删除多个键
long l2 = jedis.del("age2", "age3");
if(l2>1){
System.out.println("成功删除多个键");
}
//判断是否存在
boolean name = jedis.exists("age1");
if(name==true){
System.out.println("键存在");
}
jedis.expire("age",10L);
}
SpringBoot整合Redis
Spring Boot Data(数据) Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个类中的成员方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。
注意: 使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口
环境准备
1.引入依赖 (spring2.0以后不适用jedis了,使用lettuce)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置application.propertie
spring.redis.host=192.168.40.110
spring.redis.port=7000
spring.redis.database=0
1.2 使用StringRedisTemplate和RedisTemplate
package com.shujia.controller;
import com.shujia.entity.MyTypedTuple;
import com.shujia.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
import java.util.*;
@Controller
@ResponseBody
@RequestMapping("redistest")
public class RedisApiController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@RequestMapping("test")
public String hello(){
return "测试模板springboot";
}
@RequestMapping("test2")
public String test2(){
// RedisProperties.Pool pool = new RedisProperties.Pool();
//构建redis资源连接池配置
// JedisPoolConfig poolConfig = new JedisPoolConfig();
// //给定个数
// poolConfig.setMaxTotal(100);
//
// //根据这个配置来创建redis连接池
// JedisPool jedisPool = new JedisPool(poolConfig, "192.168.142.100", 7000);
//
// //在连接池中构建redis连接
// Jedis jedis = jedisPool.getResource();
//
// //拿着这个连接操作redis
// String name1 = jedis.get("name");
// System.out.println(name1);
//
// //操作完后将连接归还给连接池
// jedis.close();
redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
// redisTemplate.opsForValue().set("name","slm");
// Object name = redisTemplate.opsForValue().get("name");
// System.out.println(name);
//
// Student s1 = new Student("小明", 18);
// redisTemplate.opsForValue().set("1001",s1);
Object o = redisTemplate.opsForValue().get("1001");
System.out.println(o);
return "测试使用RedisTemplate对象成功";
}
@RequestMapping("testHash")
public String testHash(){
HashMap<String, String> hash = new HashMap<>();
hash.put("name","小明");
hash.put("age","18");
hash.put("like","睡觉");
hash.put("gender","男");
hash.put("address","安徽芜湖");
stringRedisTemplate.opsForHash().putAll("hash1",hash);
ArrayList<Object> li = new ArrayList<>();
li.add("name");
li.add("age");
li.add("like");
li.add("gender");
li.add("address");
List<Object> hash1 = stringRedisTemplate.opsForHash().multiGet("hash1", li);
for (Object o : hash1) {
System.out.println(o);
}
return "测试Hash类型成功!!";
}
@RequestMapping("testZSet")
public String testZSet(){
// HashSet<ZSetOperations.TypedTuple<String>> myTypedTuples = new HashSet<>();
// myTypedTuples.add(new MyTypedTuple("slm",100.0));
// myTypedTuples.add(new MyTypedTuple("wxw",90.0));
// myTypedTuples.add(new MyTypedTuple("lwc",60.0));
// myTypedTuples.add(new MyTypedTuple("lkd",80.0));
// stringRedisTemplate.opsForZSet().add("zest1",myTypedTuples);
// stringRedisTemplate.rename("zest1","zset1");
Set<String> zset1 = stringRedisTemplate.opsForZSet().range("zsett1", 0, -1);
if (zset1 != null) {
for (String s : zset1) {
System.out.println(s);
}
}
System.out.println("----------------------------------");
Set<ZSetOperations.TypedTuple<String>> zset11 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zset1", 80, 100);
if (zset11 != null) {
for (ZSetOperations.TypedTuple<String> stringTypedTuple : zset11) {
String value=stringTypedTuple.getValue();
Double score=stringTypedTuple.getScore();
System.out.println(value+":"+score);
}
}
System.out.println("=========================================");
Set<String> set = stringRedisTemplate.opsForZSet().reverseRangeByScore("zset1", 0, 1000);
if (set != null) {
for (String s : set) {
System.out.println(s);
}
}
return "测试ZSet类型成功";
}
@RequestMapping("testSet")
public String testSet(){
stringRedisTemplate.opsForSet().add("set1","hadoop","hive","ETL","Hbase","阿里云");
Set<String> set1 = stringRedisTemplate.opsForSet().members("set1");
for (String s : set1) {
System.out.println(s);
}
return "操作Set类型成功";
}
@RequestMapping("testString")
public String testString(){
stringRedisTemplate.opsForValue().set("name","slm");
stringRedisTemplate.opsForValue().set("age","18");
stringRedisTemplate.opsForValue().set("gender","男");
stringRedisTemplate.opsForValue().set("like","sleep");
stringRedisTemplate.opsForValue().set("test","test",Duration.ofSeconds(20L));
String s = stringRedisTemplate.opsForValue().get("name");
System.out.println(s);
ArrayList<String> list = new ArrayList<>();
list.add("name");
list.add("age");
list.add("like");
list.add("gender");
List<String> list1 = stringRedisTemplate.opsForValue().multiGet(list);
for (String s1 : list) {
System.out.println(s1);
}
HashMap<String, String> map = new HashMap<>();
map.put("address","anhuiwuhu");
map.put("phone","华为1998");
stringRedisTemplate.opsForValue().multiSet(map);
stringRedisTemplate.opsForValue().append("address","安徽芜湖");
// stringRedisTemplate.opsForValue().decrement("age");
stringRedisTemplate.opsForValue().increment("age", 3L);
return "测试联级请求";
}
@RequestMapping("testList")
public String testList(){
// stringRedisTemplate.opsForList().leftPush("list1","java");
// stringRedisTemplate.opsForList().leftPush("list1","linux");
// stringRedisTemplate.opsForList().leftPush("list1","mysql");
// stringRedisTemplate.opsForList().leftPush("list1","maven-git");
// stringRedisTemplate.opsForList().leftPush("list1","springboot");
// stringRedisTemplate.opsForList().leftPush("list1","redis");
// stringRedisTemplate.opsForList().leftPush("list1","hadoop");
// stringRedisTemplate.opsForList().leftPush("list1","hive");
// stringRedisTemplate.opsForList().leftPush("list1","etl");
// stringRedisTemplate.opsForList().leftPush("list1","hbase");
// stringRedisTemplate.opsForList().leftPush("list1","阿里云");
// stringRedisTemplate.opsForList().leftPush("list1","项目");
// stringRedisTemplate.opsForList().leftPush("list1","python");
// stringRedisTemplate.opsForList().leftPush("list1","spark");
// stringRedisTemplate.opsForList().leftPush("list1","fink");
ArrayList<String> list = new ArrayList<>();
list.add("java");
list.add("linux");
list.add("mysql");
list.add("maven-git");
list.add("springboot");
list.add("redis");
list.add("hadoop");
list.add("hive");
list.add("etl");
list.add("hbase");
list.add("阿里云");
list.add("项目");
list.add("python");
list.add("spark");
list.add("fink");
stringRedisTemplate.opsForList().rightPushAll("list2", list);
List<String> list1 = stringRedisTemplate.opsForList().range("list1", 0, -1);
if (list1 != null) {
for (String s : list1) {
System.out.println(s);
}
}
// stringRedisTemplate.opsForList().leftPush("list1","spark","test1");
// stringRedisTemplate.opsForList().rightPush("list1","spark","test2");
stringRedisTemplate.opsForList().trim("list1",0,5);
return "测试list类型";
}
@RequestMapping("testKey")
public String testKey(){
Set<String> keys = stringRedisTemplate.keys("*");
if (keys!=null){
for (String key : keys) {
System.out.println(key);
}
}else {
System.out.println("该库中没有键存在");
}
System.out.println("-----------------------------");
// stringRedisTemplate.expire("moname", Duration.ofMinutes(1L));
// stringRedisTemplate.expire("moname",Duration.ofMillis(20000L));
// Boolean key123 = stringRedisTemplate.hasKey("key123");
// System.out.println(key123);
// Boolean moname = stringRedisTemplate.hasKey("moname");
// System.out.println(moname);
// stringRedisTemplate.rename("moname","mname");
// stringRedisTemplate.rename("faname","fname");
// stringRedisTemplate.move("mname",1);
// System.out.println(stringRedisTemplate.type("fname"));
// System.out.println(stringRedisTemplate.type("users"));
// System.out.println(stringRedisTemplate.type("list1"));
// System.out.println(stringRedisTemplate.type("set1"));
// System.out.println(stringRedisTemplate.type("zset1"));
// System.out.println(stringRedisTemplate.type("hash1"));
// System.out.println(stringRedisTemplate.type("20190601"));
// DataType name = stringRedisTemplate.type("name");
// System.out.println(name.code());//这种方式打印结果为小写
System.out.println("-----------------------------");
Set<String> keys2 = stringRedisTemplate.keys("*");
if (keys2!=null){
for (String key : keys2) {
System.out.println(key);
}
}else {
System.out.println("该库中没有键存在");
}
return "测试与redis键相关的操作成功!!!";
}
}
package com.shujia.entity;
import org.springframework.data.redis.core.ZSetOperations;
public class MyTypedTuple implements ZSetOperations.TypedTuple<String> {
private String value;
private Double score;
public MyTypedTuple(String value,Double score) {
this.value=value;
this.score=score;
}
@Override
public String getValue() {
return value;
}
@Override
public Double getScore() {
return score;
}
@Override
public int compareTo(ZSetOperations.TypedTuple<String> o) {
return 0;
}
@Override
public String toString() {
return "MyTypedTuple{" +
"value='" + value + '\'' +
", score=" + score +
'}';
}
}
package com.shujia.entity;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = -3551806224274077921L;
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
连接池
引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
//构建redis资源连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
//给定个数
poolConfig.setMaxTotal(100);
//根据这个配置来创建redis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "192.168.40.110", 7000);
//在连接池中构建redis连接
Jedis jedis = jedisPool.getResource();
//拿着这个连接操作redis
String name1 = jedis.get("name");
System.out.println(name1);
//操作完后将连接归还给连接池
jedis.close();
Redis 主从复制
主从复制
主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据
主从复制架构图

搭建主从复制(做之前拍快照)(带同学们结合官网看redis7.0.0的做法)
第一步,创建三个目录代表三台机器,master,node1,node2

拷贝源码中的redis.conf分别到master,node1,node2中
[root@master redis-install]# cp redis-7.0.0/redis.conf ./master/
[root@master redis-install]# cp redis-7.0.0/redis.conf ./node1/
[root@master redis-install]# cp redis-7.0.0/redis.conf ./node2/
# 1.准备3台机器并修改配置,修改端口号,开启远程连接,配置主节点是谁
- master
port 6379
protected-mode no
- node1
port 6380
protected-mode no
replicaof <masterip> <masterport> # replicaof 192.168.40.110 6379
- node2
port 6381
protected-mode no
replicaof <masterip> <masterport> # replicaof 192.168.40.110 6379
# 2.启动3台机器进行测试
- cd /usr/local/soft/bigdata17/redis-install
- redis-server ./master/redis.conf
- redis-server ./node1/redis.conf
- redis-server ./node2/redis.conf



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

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

集群细节(需要画图带同学理解)
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.(半数机制,后面hadoop也会说到 )
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

集群搭建
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点。
# 1.准备环境安装ruby以及redis集群依赖
- yum install -y ruby rubygems
# https://rubygems.org/gems/redis/versions
- gem install redis-xxx.gem

# 2.在一台机器创建7个目录

# 3.每个目录复制一份配置文件
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7000/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7001/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7002/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7003/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7004/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7005/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7007/

# 4.修改不同目录配置文件
- port 7000 ..... //修改端口
- # bind 127.0.0.1 -::1 //开启远程连接
- protected-mode no
- daemonize yes //开启守护进程
- dbfilename dump-7000.rdb //每台机器的文件不能一样
- cluster-enabled yes //开启集群模式
- cluster-config-file nodes-7000.conf //集群节点配置文件
- cluster-node-timeout 5000 //集群节点超时时间
- appendonly yes //开启AOF持久化
- appendfilename "appendonly-7000.aof" //修改aof文件名
- appenddirname "appendonlydir-7000"
# 5.指定不同目录配置文件启动七个节点(7003,7004的忘记修改了守护进程)
[root@master redis-cluster]# redis-server 7000/redis.conf
[root@master redis-cluster]# redis-server 7001/redis.conf
[root@master redis-cluster]# redis-server 7002/redis.conf
[root@master redis-cluster]# redis-server 7003/redis.conf
[root@master redis-cluster]# redis-server 7004/redis.conf
[root@master redis-cluster]# redis-server 7005/redis.conf
[root@master redis-cluster]# redis-server 7006/redis.conf
# 6.查看进程 - [root@localhost bin]# ps aux|grep redis

1.创建集群
# 1.复制集群操作脚本到bin目录中 [root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/src/redis-trib.rb /usr/local/soft/redis/bin/ # 2.创建集群 redis7.0.0之前的命令:redis-trib.rb create --replicas 1 192.168.40.110:7000 192.168.40.110:7001 192.168.40.110:7002 192.168.40.110:7003 192.168.40.110:7004 192.168.40.110:7005 redis7.0.0之后的命令:redis-cli --cluster create 192.168.40.110:7000 192.168.40.110:7001 192.168.40.110:7002 192.168.40.110:7003 192.168.40.110:7004 192.168.40.110:7005 --cluster-replicas 1

# 3.集群创建成功出现如下提示

2.查看集群状态
# 1.查看集群状态 check [原始集群中任意节点] [无] redis-cli --cluster check 192.168.40.110:7000 # 2.集群节点状态说明 - 主节点 主节点存在hash slots,且主节点的hash slots 没有交叉 主节点不能删除 一个主节点可以有多个从节点 主节点宕机时多个副本之间自动选举主节点 - 从节点 从节点没有hash slots 从节点可以删除 从节点不负责数据的写,只负责数据的同步
使用集群(演示其中一个主节点宕机的状态,然后从节点接管)

3.添加主节点
# 1.添加主节点 add-node [新加入节点] [原始集群中任意节点] redis-cli --cluster add-node 192.168.40.110:7006 192.168.40.110:7000 --cluster-slave - 注意: 1.该节点必须以集群模式启动 2.默认情况下该节点就是以master节点形式添加
4.添加从节点
# 1.添加从节点 add-node --slave [新加入节点] [集群中任意节点] - redis-trib.rb add-node --slave 192.168.40.110:7006 192.168.40.110:7000 - 注意: 当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点 # 2.为确定的master节点添加主节点 add-node --slave --master-id master节点id [新加入节点] [集群任意节点] - redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000
5.删除副本节点
# 1.删除节点 del-node [集群中任意节点] [删除节点id] - redis-trib.rb del-node 192.168.40.110:7002 0ca3f102ecf0c888fc7a7ce43a13e9be9f6d3dd1 - 注意: 1.被删除的节点必须是从节点或没有被分配hash slots的节点
java和springboot分别操作集群
1. java操作
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>redis-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
代码编写
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7000));
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7001));
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7002));
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7003));
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7004));
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7005));
jedisClusterNode.add(new HostAndPort("192.168.40.110", 7006));
JedisCluster jedisCluster = null;
try {
//connectionTimeout:指的是连接一个url的连接等待时间
//soTimeout:指的是连接上一个url,获取response的返回等待时间
jedisCluster = new JedisCluster(jedisClusterNode, config);
Set<String> keys = jedisCluster.keys("*");
System.out.println(keys);
System.out.println(jedisCluster.set("age", "18"));
System.out.println(jedisCluster.get("age"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCluster != null)
jedisCluster.close();
}
2. Springboot操作
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shujia</groupId>
<artifactId>pringboot-redis-demo1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>pringboot-redis-demo1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入spring data redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
修改配置文件
server:
port: 8989
spring:
redis:
# host: 192.168.40.110
# port: 7000
# database: 0
cluster:
nodes: 192.168.40.110:7000,192.168.40.110:7001,192.168.40.110:7002,192.168.40.110:7003,192.168.40.110:7004,192.168.40.110:7005,192.168.40.110:7006
编写代码
Set<String> keys = stringRedisTemplate.keys("*");
System.out.println(keys);
Springboot整合mysql,Redis案例
1、新建项目



或者从官网创建

2、引入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.shujia</groupId> <artifactId>springboot-anlidemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-anlidemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--让内嵌tomcat具有解析jsp功能--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!--c标签库--> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--添加MySql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--添加JDBC依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--使用thymelaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <!--mybatis相关--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <!-- 获取页面session对象request对象response对象 HttpServletXXX jar包--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> <!-- 获取页面session对象request对象response对象 HttpServletXXX jar包--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3、修改配置文件
# =========================修改服务器相关============================
server:
port: 8083
servlet:
context-path: /bigdata17
tomcat:
uri-encoding: UTF-8
# =====================新增数据库配置=============================
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.40.110:3306/bigdata17?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
thymeleaf:
cache: false # 关闭缓存
prefix: classpath:/templates/ #指定模板位置
suffix: .html #指定后缀
redis:
# host: 192.168.40.110
# port: 7000
# database: 0
cluster:
nodes: 192.168.40.110:7000,192.168.40.110:7001,192.168.40.110:7002,192.168.40.110:7003,192.168.40.110:7004,192.168.40.110:7005,192.168.40.110:7006
# ====================新增mybits配置=========================
mybatis:
configuration:
map-underscore-to-camel-case: true #下划线驼峰设置
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL语句
# ==================设置日志级别==============================
logging:
level:
root: info #指定根日志级别(一般不推荐修改根日志,输出信息太多,推荐使用子日志)
4、创建分层

5、运行测试没有问题后,一切准备就绪
6、第一个案例,用户登录案例(springboot结合mysql)
1. 创建数据库表,用户表(注意,主键设置成自增,方便后续查找或者删除)

2. 创建实体类,对应数据库表结构(规范是成员变量与表字段一样,大小写可以不一样)
package com.shujia.entity;
public class User {
private Integer id;
private String username;
private String password;
public User(Integer id, String username, String password) {
this.id=id;
this.username=username;
this.password=password;
}
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
3. 编写对用户的方法接口(在service层中)
package com.shujia.service;
import com.shujia.entity.User;
import java.util.List;
public interface UserService {
//根据账户名和密码查找
User findByNameAndPwd(String name,String password);
}
4. 编写对接口的实现类(在service层中的impl包中),同时编写dao层的数据库操作类
package com.shujia.dao;
import com.shujia.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserDao {
//根据账户名和密码查找
@Select("select * from user where username=#{username} and password=#{password}")
public User findByNameAndPwd(String username,String password);
}
package com.shujia.service.impl;
import com.shujia.dao.UserDao;
import com.shujia.entity.User;
import com.shujia.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User findByNameAndPwd(String name,String password) {
return userDao.findByNameAndPwd(name,password);
}
}
5. 编写控制类
package com.shujia.controller;
import com.shujia.entity.User;
import com.shujia.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Controller
public class UserController {
@Autowired
private UserService userService;
//新增登录页面
@GetMapping("/index.html")
public String userLogin(){
return "login";
}
//查询用户
@PostMapping("/login")
public String login(User user,Model model) {
// userService.save(user);
User byName = userService.findByNameAndPwd(user.getUsername(),user.getPassword());
if(byName!=null){
//表示重置index.html界面并且跳转到index.html界面
return "index";
}else {
return "login";
}
}
}
6. 编写登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<style>
a{
color: #ffffff;
}
h2{
/*文字对齐*/
text-align: center;
}
</style>
<body>
<h2>17期springboot案例-登录</h2>
<div style="width:600px;height:100%;margin-left:270px;">
<form th:action="@{/login}" method="post">
<!-- form-control给input添加这个class后就会使用bootstrap自带的input框-->
用户名:<input class="form-control" type="text" th:value="${username}" name="username"><br>
<!--注意参数的拼接-->
密 码:<input class="form-control" type="password" th:value="${password}" name="password"><br>
<button class="btn btn-primary btn-lg btn-block">保存</button>
</form>
</div>
</body>
</html>
首页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<style>
a{
color: #ffffff;
}
h1{
/*文字对齐*/
text-align: center;
}
</style>
<body>
<!--一开始写一个登录成功-->
<h2>登录成功</h2>
</body>
</html>


浙公网安备 33010602011771号