Redis 基础知识和基本使用(个人笔记)

138 阅读47分钟

Redis学习笔记(个人笔记)


Redis 基础知识和使用

0X01#Redis基本常识

Redis 是Server-Client架构Key-Vlue型内存数据库,Redis安装成功后提供的redis-serverredis-cli则分别为其服务端和客户端。内存数据库具备内存高速读写的物理优势,读写速度同MySQL也非同一量级。同时也具备内存数据库的通病:断电容易丢失数据。但Redis也提供持久化策略应对,并非一定会丢失数据。

Redis-cli连接数据库

基本格式: redis-cli -h <host> -p <port> -a <password>

<host>:服务主机名 (IP地址,域名...)

<port>: 端口号

<password> :密码

示例:

root@DESKTOP:/usr/local/src/REDIS/redis-6.2.3/MYCONF# redis-cli -h localhost  -p 6379  -a wsharkcoder
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
localhost:6379>

连接测试

PING 通常也是判断Redis机器连通性的判断依据!

localhost:6379> PING
PONG
Redis默认有16个数据库

Redis.conf 配置文件可查看到关于DB数目的信息

# 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
#设置数据库的数目.默认数据库是0号,你可以在每个连接的基础上使用 SELECT <DBID> 来选择
#其他数据库,其中<DBID>是一个介于0和database-1之间的数字。
databases 16
                                                      199,1         17%

0X01-01#常规命令

Redis数据库作为No-SQL类型数据库,也意味着无法用标准的SQL语句进行数据的增删改查,但Redis数据库提供相应的命令行操作数据的存入与读取。下面先介绍些基本且常见的操作来对数据进行存储与查询。

选择数据库

基本格式: select <index>

localhost:6379> select  1   #选择1号数据库
OK                          
localhost:6379[1]> select 0 #选择0号数据库
OK
设置 键值对

基本格式:set <key> <value>

localhost:6379[1]> set name val #设置键值对name-val
OK                           
查看当前数据库键值对数目

基本格式:dbsize

localhost:6379> dbsize    
(integer) 1                  #返回本数据库键值对数目
查看当前数据库所有键值

基本格式: keys *

localhost:6379> keys *
1) "name"
删除 本数据库 所有键值对

基本格式: flushdb

#0号数据库设置键值对
localhost:6379> select 0          
OK
localhost:6379> set name val
OK
#1号数据库设置键值对
localhost:6379> select 1
OK
localhost:6379[1]> set  name val
OK
#flushdb删除0号数据库所有键值对
localhost:6379[1]> select 0
OK
localhost:6379> flushdb
OK                                  
#查看0号数据库键值对是否清除
localhost:6379> keys *
(empty array)
#查看1号数据库键值对并未清除
localhost:6379> select  1
OK
localhost:6379[1]> keys *
1) "name"
删除 所有数据库 所有键值对

基本格式: flushall

#0号数据库设置键值对
localhost:6379> select 0
OK
localhost:6379> set name wsharkcoder
OK
#1号数据库设置键值对
localhost:6379> select 1
OK
localhost:6379[1]> set name wsharkcoder
OK
#0号数据库中删除所有数据库
localhost:6379[1]> select 0
OK
localhost:6379> flushall
OK
localhost:6379> keys *
(empty array)
#判断1号数据库中元素是否均删除
localhost:6379> select 1
OK
localhost:6379[1]> keys *
(empty array)
判断 键值对是否存在

基本格式:EXISTS <Key>

localhost:6379> set name "WSharkCoder" #设置键值对name-"WSharkCoder"
OK
localhost:6379> exists name            #判断键值对是否存在
(integer) 1                  #返回0表示不存在 1表示存在
移动本数据库键值对至其他数据库

基本格式:move <key> <db>

#0号数据库中设置键值对
localhost:6379> select 0
OK
localhost:6379> set name "WSharkCoder"
OK
#移动本数据中键值对至1号数据库 
localhost:6379> move name 1
(integer) 1                      #返回值0表示移动成功 1表示失败
localhost:6379> keys *
(empty array)
localhost:6379> select 1
OK
localhost:6379[1]> keys *
1) "name"
删除本数据库中键值对

基本格式: del <key> [<key>...]

#设置键值对
localhost:6379> set name "WSharkCoder"
OK
localhost:6379> set age 22
OK
localhost:6379> keys *
1) "name"
2) "age"
​
#删除键值对
localhost:6379> del name age
(integer) 2                      #返回值表示删除成功的键值对数目
localhost:6379> keys *
(empty array)
设置 键值对 过期时间

基本格式:expire <key> <seconds>

localhost:6379> set name "WSharkCoder" #设置键值对name-"WSharkCoder"
OK
localhost:6379> expire name 10         #设置键值对name-"WSharkCoder" 过期时间10秒钟
(integer) 1                            #0表示设置过期时间失败 1表示成功
localhost:6379> ttl name               #查看键值对过期时间
(integer) 8
查看 键值对 过期时间

基本格式:ttl <key>

返回值:过期时间

localhost:6379> set name "WSharkCoder" #设置键值对name-"WSharkCoder"
OK
localhost:6379> expire name 10         #设置键值对name-"WSharkCoder" 过期时间10秒钟
(integer) 1
localhost:6379> ttl name               #查看键值对过期时间
(integer) 8
查看 键值对 数据类型

基本格式:type <key

localhost:6379> set name wsharkcoder   
OK
localhost:6379> type name
string

基本操作中除set之外均是与特定的数据类型不相关的操作,为后续其他操作时作基础~

0X02#Redis数据类型及命令

想必只要在面试中被面到Redis的朋友萌一定会被问到:Redis中有几种数据类型?又分别是哪些?

五种。StringHashListSetSortedList

虽然绝大多数人都按上述答案回答,但实际上Redis 中数据类型的数目并不止五种,常用的数据类型包括:StringListSetHashZSetGeospatialHyperloglogBitMap 。每种数据类型的操作方式都不太相同但均类似,类比熟悉即可事半功倍。

0X02-01#String

设置 键值对

基本格式: set <key> <value>

localhost:6379> set name "WSharkCoder" #设置键值对 name-:WSharkCoder"
OK                                    
localhost:6379> keys *
1) "name"
localhost:6379> set name "PIG"        #重复设置键值对name-"PIG" 
OK                                    #重复设置键值对均会覆盖原键值对
localhost:6379> get name              #获取键的值
"PIG"
获取 键对应的值

基本格式:get <Key>

localhost:6379> set name "PIG"      
OK                              
localhost:6379> get name              #获取键的值
"PIG"
键对应值后追加字符串

基本格式:append <Key> <Value>

注意:

若本数据库不存在该键值对,则在数据库设置该键值对;

当本数据库存在该键值对,才在值的尾部追加字符串。

localhost:6379> set name "Cat_"     
OK
localhost:6379> append name "Tom"       #在键值对name-"Cat_"值后追加字符串"Tom"
(integer) 7                             #返回追加后字符串长度
localhost:6379> get name
"Cat_Tom"
localhost:6379> keys *
1) "name"                               #数据库中不存在age键值对
localhost:6379> append age "22"         #在键值对age中值追加字符串"22"
(integer) 2
localhost:6379> get age                 #数据库中被设置键值对"22"
"22"
计算 键对应值 字符串长度

基本格式:strlen <key>

localhost:6379> set name "WSharkCoder"  #设置键值对name-"WSharkCoder"
OK
localhost:6379> strlen name             #计算 name对应的值的字符串长度
(integer) 11
截取 值 字符串子串

基本格式:getrange <key> <start> <end>

<start> :起始下标

<end>:结束下标

#截取身份证前3位判断地区模拟
localhost:6379> set ID "420116..."
OK
localhost:6379> getrange ID  0 2    #范围下标从0开始
"420"
localhost:6379> getrange ID  0 -1   #范围结束值-1则返回整字符串
"420116..."
替换 值 字符串

基本格式:setrange <key> <offerset> <value>

<offerset>:偏移量 (偏移量==0则直接从头部开始覆盖字符串)

<value>:替换值

localhost:6379> set ID  "420115..."
OK
localhost:6379> setrange ID 2 116    #修改上述ID值
(integer) 9                          #注意:返回替换后的字符串长度
localhost:6379> get ID
"421165..."                          #注意偏移量设置问题
VAL++操作

基本格式:incr <key>

#点击量统计模拟
localhost:6379> set TIMES  "0"         #初始点击次数 "0"次
OK
localhost:6379> incr TIMES             #点击数++
(integer) 1                            #注意:返回++后的数目
localhost:6379> set TIMES "S1"         #重新设置点击次数 "S1"
OK
localhost:6379> incr TIMES             #点击数++ 操作失败
(error) ERR value is not an integer or out of range #值超出Integer范围
VAL--操作

基本格式:decr <key>

#库存删减模拟
localhost:6379> set TOTAL "10"          #初始库存 "10"
OK
localhost:6379> decr TOTAL              #库存--
(integer) 9
含步长VAL++操作

基本格式:incrby <key> <increment>

#批销售-销售量模拟
localhost:6379> set SELL_NUM "10"    #初始销售额 "10"
OK
localhost:6379> incrby SELL_NUM 10   #单次销售+"10"
(integer) 20
localhost:6379> get SELL_NUM
"20"
含步长VAL--操作

基本格式:decrby <Key> <Decrment>

#批销售-库存量模拟
localhost:6379> set TOTAL "500"     #初始库存 "500"
OK
localhost:6379> decrby TOTAL 100    #单次出库-"100"
(integer) 400
localhost:6379> get TOTAL
"400"
设置 键值对以及过期时间

基本格式:setex <key> <seconds> <value>

setex等价于set with expire,即set命令与expire命令连续使用~

localhost:6379> setex name 100 "WSharkCoder"
OK
localhost:6379> ttl name
(integer) 93
不存在就设置键值对 存在则不设置

基本格式 :setnx <key> <value>

本命令实现功能表述起来比较拗口,而实现的功能主要是:

本数据库中不存在则设置成功,否则设置失败

setnx === set with no exists

localhost:6379> setnx name "WSharkCoder"        #不存在设置键值对
(integer) 1                                     #返回0表示失败 1表示成功
localhost:6379> exists name
(integer) 1
localhost:6379> setnx  name "TOM"
(integer) 0
设置 多组键值对

基本格式:mset <Key> <Value> [<Key> <Value>...]

localhost:6379> mset name "WSharkCoder" age "21" #设置多对键值对
OK
localhost:6379> get name
"WSharkCoder"
localhost:6379> get age
"21"
获取 多组键值对

基本格式: mget <Key> [<Key>...]

localhost:6379> mget name age
1) "WSharkCoder"
2) "21"
不存在则设置 存在则不设置 多组键值对

基本格式:msetnx <Key> <Value> [<Key> <Value>...]

本命令具有原子性,要么所有键值对全设置成功,要么所有键值对全设置失败

localhost:6379> set name "WSharkCoder"          #设置键值对name-"WSharkCoder"
OK
localhost:6379> msetnx name "TOM" age "22"      #设置多组键值对name-"WSharkCoder" 
(integer) 0                                     #设置失败:name-"WSharkCoder"已存在
localhost:6379> exists age
(integer) 0
localhost:6379> flushdb                         #清楚数据库
OK
localhost:6379> msetnx name "TOM" age "22"
(integer) 1
localhost:6379> keys *
1) "name"
2) "age"
存储对象
JSON格式
#存储JSON格式字符串
localhost:6379> set User:01 "{name:WSharkCoder,age:21}"
OK
localhost:6379> get User:01                  #获取用户对象
"{name:WSharkCoder,age:21}"
特殊命名方式
localhost:6379> mset user:01:name "WSharkCoder" user:01:age "21"
OK
localhost:6379> mget user:01:name user:01:age
1) "WSharkCoder"
2) "21"
获取设置 组合命令

基本格式: getset <key> <value>

本数据库中不存在键值对,则返回nil并设置键值对;反之,存在键值对则返回旧值,并更新新值

localhost:6379> set name "WSharkCoder"
OK
localhost:6379> getset name "TOM"
"WSharkCoder"
localhost:6379> get name
"TOM"
应用简介

Redis中String数据类型不仅能存储String类型的数据,还能够存储 Integer类型的数据并进行运算,此功能常用作网站点击量,视频播放计数器等功能的实现。setnx以及msetnx命令常用来构建分布式锁,完成对分布式资源的访问控制等。

0X02-02#List

Redis中List数据结构采用双端链表实现,因此操作可从链表首部和尾部进行,也可借助List实现队列和栈结构。

链表头部插入

基本格式:lpush <Key> <Value> [<value>...]

localhost:6379> lpush color red green       #头部插入 
(integer) 2
localhost:6379> lrange color 0 -1           #获取链表所有元素
1) "green"
2) "red"
链表尾部插入

基本格式:rpush <Key> <Value> [<Value>...]

localhost:6379> rpush color red green      #尾插入
(integer) 2
localhost:6379> lrange color 0 -1          #获取链表所有元素
1) "red"
2) "green"
获取链表范围内元素

基本格式:lrange <Key> <Start> <Stop>

localhost:6379> lpush List A B C D
(integer) 4
localhost:6379> lrange List 0 -1           #获取链表所有元素
1) "D"
2) "C"
3) "B"
4) "A"
localhost:6379> lrange List 0 1            #获取链表部分元素
1) "D"
2) "C"
移除链表首部元素

基本格式: lpop <Key> [<count>]

<count>: POP元素的个数

localhost:6379> lpush List A B C D         #链表头部插入元素
(integer) 4
localhost:6379> lpop List 3                #链表头部POP3个元素
1) "D"
2) "C"
3) "B"
移除链表尾部元素

基本格式:rpop <Key>

localhost:6379> lpush List A B C D         #链表头部插入元素
(integer) 4
localhost:6379> rpop List 1                #链表尾部POP1个元素
1) "A"
获取链表下表对应元素

基本格式:lindex <key> <index>

localhost:6379> rpush List A B C D E F     #链表尾部插入元素 
(integer) 6
localhost:6379> lindex List 2              #获取链表下标2的元素
"C"
获取链表长度

基本格式:llen <key>

localhost:6379> rpush List A B C D E F    #尾插入链表
(integer) 6
localhost:6379> llen List                 #计算链表长度
(integer) 6
移除指定个数的某个值

基本格式:lrem <Key> <count> <value>

localhost:6379> rpush List A A A B B C C D D #尾插入数据至链表
(integer) 9
localhost:6379> lrem List 2 A                #移除2个元素A
(integer) 2
localhost:6379> lrange List 0 -1
1) "A"
2) "B"
3) "B"
4) "C"
5) "C"
6) "D"
7) "D"
截取链表

基本格式:ltrim <key> <start> <end>

localhost:6379> rpush List A B C D E      #尾插入元素
(integer) 5
localhost:6379> ltrim List 0 2            #截取链表元素
OK
localhost:6379> lrange List 0 -1
1) "A"
2) "B"
3) "C"
本链表POP元素并PUSH至其他链表

基本格式:rpoplpush <Source> <Destination>

<Source>:源链表

Destination:目标链表

#注意:链表数据类型同类型才能进行该操作
localhost:6379> rpush L1  1 2 3 4      
(integer) 4
localhost:6379> rpush L2 A B C D
(integer) 4
localhost:6379> rpoplpush L2 L1        #POPL2尾元素并PUSH至L1首部
"D"
localhost:6379> lrange  L1 0 -1
1) "D"
2) "1"
3) "2"
4) "3"
5) "4"
localhost:6379> lrange L2 0 -1
1) "A"
2) "B"
3) "C"
替换链表下标对应值

基本格式:lset <Key> <index> <value>

localhost:6379> rpush L A B C D   #链表添加元素
(integer) 4
localhost:6379> lset L 1 T        #替换链表下标1的元素为T
OK
localhost:6379> lrange L 0 -1
1) "A"
2) "T"
3) "C"
4) "D"
指定元素前或后插入元素

基本格式:linsert <Key> before|after <pivot> value

localhost:6379> rpush LIST A B C            #链表添加元素
(integer) 3
localhost:6379> linsert LIST BEFORE C D     #链表中元素C之前插入元素D
(integer) 4
localhost:6379> lrange LIST 0 -1
1) "A"
2) "B"
3) "D"
4) "C"
应用简介

Redis中List数据类型常用作构成消息对列,以及最新消息列表等功能的实现。

0X02-03#Set

Set数据类型与数学意义上的集合一致,即不包含重复元素的集合。

添加元素至集合中

基本格式:sadd <key> <member> [<memeber>...]

localhost:6379> sadd sex man woman     #向集合sex添加元素man,woman
(integer) 2
查看集合中所有元素

基本格式:smembers <key>

localhost:6379> sadd sex man woman
(integer) 2
localhost:6379> smembers sex          #查看集合中所有元素
1) "woman"
2) "man"
判断集合是否包含某元素

基本格式:sismember <key> <member>

localhost:6379> sadd sex man woman  #添加元素至集合中
(integer) 2
localhost:6379> sismember sex man   #判断集合sex中是否包含元素man
(integer) 1
计算集合元素个数

基本格式:scard <Key>

localhost:6379> sadd Color red green blue    #添加元素至集合中
(integer) 3
localhost:6379> scard Color                  #计算集合中元素个数
(integer) 3
移除集合元素

基本格式:srem <key> <member> [<member>...]

localhost:6379> sadd color red green yellow #添加元素至集合中
(integer) 3
localhost:6379> srem color green yellow     #移除集合中部分元素
(integer) 2
localhost:6379> smembers color              #查看集合所有元素
1) "red"
随机抽取集合中N个元素

基本格式:srandmember <key> [<count>]

<count>:抽取元素的个数,默认值1个

localhost:6379>  sadd color  red green blue black white
(integer) 4
localhost:6379> srandmember color 2 #随机抽取集合中2个元素
1) "white"
2) "green"
localhost:6379> srandmember color 2 #随机抽取集合中2个元素
1) "red"
2) "green"
随机删除集合中N个元素

基本格式:spop <key> <count>

localhost:6379> sadd color red green blue balck white
(integer) 5
localhost:6379> spop color 2        #随机删除集合中2个元素
1) "green"
2) "red"
localhost:6379> spop color 2        #随机删除集合中2个元素
1) "blue"
2) "balck"
localhost:6379> smembers color
1) "white"
移动本集合元素至其他集合中

基本格式:smove <source> <destination> <member>

localhost:6379> sadd color blue white black green red yellow #颜色集合
(integer) 6
localhost:6379> sadd NUM 1 2  3                              #数字集合
(integer) 3
#将颜色集合中元素移动至数字集合中
localhost:6379> smove color  NUM blue                        
(integer) 1                                                  
localhost:6379> smembers NUM
1) "blue"
2) "2"
3) "3"
4) "1"
集合操作:差 交 并
差集

基本格式:sdiff <Key> [<Key>...]

交集

基本格式:sinter <Key> [<Key>...]

并集

基本格式:sunion <Key> [<Key>...]

localhost:6379> sadd N1 1 2 3
(integer) 3
localhost:6379> sadd N2 3 4 5
(integer) 3
localhost:6379> sdiff N1 N2     #差集N1-N2
1) "1"
2) "2"
localhost:6379> sinter N1 N2    #交集
1) "3"
localhost:6379> sunion N1 N2    #并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
应用简介

Set集合中元素不允许重复出现,因此可用来统计访问网站的IP数目,同时Set集合操作部分可用来实现共同好友等功能。

0X02-04#Hash

设置Hash表以及域-值对
localhost:6379> hset Person name "WSharCoder" age "21" #Hash表不存在就设置Hash表并添加表内键值对
(integer) 2
localhost:6379> hset Person age "22" #Hash表存在就更新Hash表内键值对
(integer) 0
localhost:6379> hget Person name
"WSharCoder"
localhost:6379> hget Person  age
"22"
获取Hash表域对应值

基本格式:hget <key> <field>

localhost:6379> hset Person name "WSharCoder" age "21"
(integer) 2
localhost:6379> hget Person name
"WSharCoder"
设置Hash表以及多对域-值对

基本格式:hmset <key> <value> [<key> <value>...]

localhost:6379> hmset person name WSharkCoder age 21    #设置Hash表以及多组域-值对
OK
localhost:6379> hget person name
"WSharkCoder" 

命令实行结果上与hset命令毫无区别,官方解释如下:

Sets the specified fields to their respective values in the hash stored at key. This command overwrites any specified fields already existing in the hash. If key does not exist, a new key holding a hash is created.

As per Redis 4.0.0, HMSET is considered deprecated. Please prefer HSET in new code.

翻译:

将指定字段设置为存储在键中的散列中的各自值。此命令将覆盖散列中已经存在的任何指定字段。如果key不存在,则创建一个包含散列的新键。

根据Redis 4.0.0, HMSET被认为是不赞成的。请在新代码中选择HSET。

获取Hash表多个域对应域值

基本格式:hmget <key> <field> [<field>...]

localhost:6379> hset person name "WSharkCoder" age "21"
(integer) 2
localhost:6379> hmget person name age                   #获取Hash表多个域对应的域值
1) "WSharkCoder"
2) "21"
获取Hash表中所有域对应域值

基本格式:hgetall <key>

localhost:6379> hset person name "WSharkCoder" age "21"
(integer) 2
localhost:6379> hgetall person #返回Hash表内域-域值 
1) "name"                   
2) "WSharkCoder"
3) "age"
4) "21"
删除Hash表中指定域

基本格式:hdel <Key> <field> [<filed>...]

localhost:6379> hset person name "WSharkCoder" age "21"
(integer) 2
localhost:6379> hdel person name      #删除Hash表中name域
(integer) 1
localhost:6379> hgetall person
1) "age"
2) "21"
计算Hash表长度

基本格式:hlen <key>

localhost:6379> hset u_info name WSharkCoder age  21
(integer) 2
localhost:6379> hlen u_info  #计算Hash表中域值对数目
(integer) 2
判断Hash表中是否存在指定域

基本格式:hexists <key> <value>

localhost:6379> hset u_info name WSharkCoder age  21
(integer) 2
localhost:6379> hexists u_info name  #判断Hash表u_info中是否存在域name
(integer) 1
获取所有域名

基本格式:hkeys <key>

localhost:6379> hset Person name WSharkCoder age 21 
(integer) 2
localhost:6379> hkeys Person        #获取Hash表Person中所有域名
1) "name"
2) "age"
获取所有域值

基本格式:hvals <key>

localhost:6379> hset Person name WSharkCoder age 21 
(integer) 2
localhost:6379> hvals Person      #获取Hash表Person中所有域值
1) "WSharkCoder"
2) "21"
Hash表域值含步长++操作

基本格式:hincrby <key> <field> <increment>

localhost:6379> hset Person name WSharkCoder age 21
(integer) 2
localhost:6379> hincrby Person age 1    #用户年龄+1
(integer) 22
不存在则设置域 存在则不设置

基本格式:hsetnx <key> <field> <value>

localhost:6379> hsetnx Person name WSharkCoder #用户名称信息不存在时设置成功
(integer) 1
localhost:6379> hsetnx Person name WSharkCoder #用户名称信息存在时设置不成功
(integer) 0
应用简介

Hash数据类型同String类型JSON格式存储对象信息一样,也可用来存储热点数据信息等。

0X02-04#Zset

Zset数据类型也即是Redis中Sorted Set排序集合,集合中元素不仅包含元素值,还包含用以排序的数据的权值。

添加元素至集合

基本格式:zadd <key> <score> <member> [<score> <member>...]

<score>:排序权值 (Zset集合内元素默认按升序排列)

<member>:元素值

localhost:6379> zadd Salary 1200 zhangsan 3400 lisi  #有序集合中插入元素 默认元素按权值升序排列
(integer) 2
获取排序后指定位次范围元素
升序

基本格式:zrange <key> <start> <stop> [<withscores>]

<start>:起始位次

<stop>:终止位次

<withscores>:是否返回权值

localhost:6379>  zadd Salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang
(integer) 5
localhost:6379> zrange Salary 0 1   #获取工资集合中工资最低2位的姓名
1) "huang"
2) "chen"
localhost:6379>  zrange Salary  0 -1#获取工资集合升序排列的用户姓名
1) "huang"
2) "chen"
3) "wang"
4) "zhang"
5) "li"
降序

基本格式:zrevrange <key> <start> <stop> [<withscores>]

<start>:起始位次

<stop>:终止位次

<withscores>:是否返回权值

localhost:6379>  zadd Salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang
(integer) 5
localhost:6379> zrevrange Salary  0 1  #获取工资集合中工资最高2位的姓名
1) "li"
2) "zhang"
localhost:6379> zrevrange Salary  0 -1 #获取工资集合中降序排列的用户姓名
1) "li"
2) "zhang"
3) "wang"
4) "chen"
5) "huang"
获取指定权值范围内的元素
升序

基本格式:zrangebyscore <key> <min> <max> [<withscores>]

<min>:权值最小值 (特殊值:负无穷+inf

<max>:权值最大值 (特殊值:正无穷-inf

<withscores>:是否返回权值

localhost:6379>  zadd Salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang
(integer) 5
localhost:6379> zrangebyscore Salary 1550 1770 #获取工资位于1550-1770的员工姓名
1) "chen"
2) "wang"
localhost:6379> zrangebyscore Salary -inf +inf #获取所有员工的姓名
1) "huang"
2) "chen"
3) "wang"
4) "zhang"
5) "li"
降序

基本格式:zrevrangebyscore <key> <max> <min> [<withscores>]

<max>:权值最大值 (特殊值:正无穷-inf

<min>:权值最小值 (特殊值:负无穷+inf

<withscores>:是否返回权值

localhost:6379> zadd salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang 
(integer) 5
localhost:6379> zrevrangebyscore Salary 2000 1000 WITHSCORES #返回工资介于2k-1k之间的用户姓名与工资
1) "wang"
2) "1700"
3) "chen"
4) "1600"
5) "huang"
6) "1500
移除有序集合中元素

基本格式:zrem <key> <member> [<member>...]

localhost:6379>  zadd Salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang
(integer) 5
localhost:6379> zrem Salary li huang        #移除员工li huang
(integer) 2
localhost:6379> zrange Salary  0 -1         #查看剩余所有员工
1) "chen"
2) "wang"
3) "zhang"
计算有序集合中元素个数

基本格式:zcard <key>

localhost:6379>  zadd Salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang
(integer) 5
localhost:6379> zcard Salary               #返回有序集合中的元素个数
(integer) 5
计算有序集某范围内的元素个数

基本格式:zcount <key> <min> <max>

<min>:最小值

<max>:最大值

localhost:6379>  zadd Salary 2400 li 2300 zhang 1700 wang 1600 chen 1500 huang
(integer) 5
localhost:6379> zcount Salary 1000 2000 #计算工资介于1k-2k之间的人员数目
(integer) 3
应用简介

Redis中ZSet有序集合数据结构具备丰富的排序操作,因此常常用以构建排行榜等。

0X02-04#Geospatial

Geospatial数据类型用来处理地理位置信息即及维度信息,并利用经纬度信息计算相关的距离以及范围信息。

补充:

经纬度没有精度限制,可以精确到任意精度。民用北斗系统精确度已于2020年提升到1.2米,民用GPS于多年前以保持10米的精度!!

添加经纬度信息

基本格式:geoadd <key> <longitude> <latitude> <member> [<longitude> <latitude > <member>...]

<longitude>: 经度 <latitude>: 维度 <member>: 元素

注意:地球两级(北极和南极)无法直接添加

localhost:6379> geoadd China:city 121.472644 31.231706 shanghai
(integer) 1
localhost:6379> geoadd China:city 114.3475 30.49989 wuhan
(integer) 1
localhost:6379>
查看元素经纬度信息

基本格式:geopos <key> <member>[<member>...]

localhost:6379> geoadd China:city 121.472644 31.231706 shanghai
(integer) 1
localhost:6379>  geoadd China:city 114.3475 30.49989 wuhan
(integer) 1
localhost:6379> geopos China:city shanghai
1) 1) "121.47264629602432251"
   2) "31.23170490709807012"
计算元素之间的距离

geodist <key> <member> <member> [unit]

unit: 单位(m/km/mi/ft)[米/千米/英里/英尺]

localhost:6379> geoadd China:city 121.472644 31.231706 shanghai
(integer) 1
localhost:6379> geoadd China:city 114.3475 30.49989 wuhan
(integer) 1
#计算上海和武汉之间的距离
localhost:6379> geodist China:city shanghai wuhan km 
"684.9909"
以某经纬度信息为中心查找周围元素

基本格式:georadius <key> <longitude> <latitude> <radius> m|km|ft|mi [withcoord][withdist] [count <nums>]

<radius>: 半径

<withcoord>: 返回结果包含经纬度

<withdist>: 返回结果包含距离信息

count <num>: 限定查找的元素个数

localhost:6379> geoadd China:city 121.472644 31.231706 shanghai
(integer) 1
localhost:6379> geoadd China:city 114.3475 30.49989 wuhan
(integer) 1
#查找距离 经度:121  维度: 31 100 公里的城市
localhost:6379> georadius China:city 121 31 100 km 
1) "shanghai"
#限制查找数目唯一 
localhost:6379> georadius China:city 121 31 100 km  count 1
1) "shanghai"
以某元素经纬度为中心查找周围元素

基本格式:georadiusbymember <key> <member> <radius> m|km|ft|mi [withcoord ] [withdist] [count <nums>]

<member>:中心元素

<radius>:半径

<withcoord>: 返回结果包含经纬度

<withdist>: 返回结果包含距离信息

count <num>: 限定查找的元素个数

localhost:6379> geoadd China:city 121.472644 31.231706 shanghai
(integer) 1
localhost:6379> geoadd China:city 114.3475 30.49989 wuhan
(integer) 1
localhost:6379>georadiusbymember China:city shanghai 1000 km
1) "wuhan"
2) "shanghai"
返回元素经纬度11位字符Geohash字符串

基本格式:geohash <key> <member> [<member>...]

localhost:6379> geoadd China:city 121.472644 31.231706 shanghai
(integer) 1
localhost:6379> geoadd China:city 114.3475 30.49989 wuhan
(integer) 1
localhost:6379> geohash China:city shanghai 
1) "wtw3sjt9v0"
注意

GEO类型底层基于ZSet数据类型实现,因此针对ZSet类型的操作均可用于操作GEO数据类型~

应用简介

Geospatial数据类型记录着地理经纬度信息,常用来计算用户地理位置信息,距离以及附近用户等功能。

0X02-05#Hyperloglog

Redis中Hyperloglog数据类型官方解释如下:

HyperLogLog是一种概率数据结构,用于对唯一事物进行计数(从技术上讲,这是指估计集合的基数)。通常,对唯一项目进行计数需要使用与要计数的项目数量成比例的内存量,因为您需要记住过去已经看到的元素,以避免多次对其进行计数。但是,有一组算法会以内存换取精度:您最终会得到带有标准误差的估计量度,在Redis实现的情况下,该误差小于1%。该算法的神奇之处在于,您不再需要使用与所计数项目数量成正比的内存量,而是可以使用恒定数量的内存!在最坏的情况下为12k字节,如果您的HyperLogLog(从现在开始将它们称为HLL)看到的元素很少,则少得多。

什么是集合的基数?

举例:

集合A={1,3,5,7,8,7}

集合B={1,3,5,7,8}

集合A B的基数均是5,即非重复元素的个数,并允许误差的出现。

官方文档也解释道Hyperloglog的实现误差小于1%,并在需要计数大量时仅需要恒定数量的内存。

应用举例:

统计网页的UV(Unique Visitor) (网页访问人数)

传统使用前面的Set数据结构保存所有用户ID,然后最后统计Set中元素的数量作为判断标准,但内存消耗和访问人数成正比,大量用户访问网站时占用大量的内存。

添加元素

基本格式:PFadd <key> <element> [<element>...]

localhost:6379> PFAdd  UV_A u1 u2 u2 u3 #访问A网站的用户ID 
(integer) 1
计算元素数目

基本格式:PFCount <key> [<key>...]

注意:可计算单个Hyperloglog元素数目也可计算多个的元素数目

localhost:6379> PFAdd  UV_A u1 u2 u2 u3 #访问A网站的用户数目
(integer) 1
localhost:6379> PFCount UV_A            #计算A网站访问用户数目(非重复统计)
(integer) 3
localhost:6379> PFAdd UV_B u2 u3 u4
(integer) 1
localhost:6379> PFCount UV_A UV_B       #访问A网站和B网站的用户数目(非重复统计)
(integer) 4
合并某Hyperloglog统计结果至另一Hyperloglog中

基本格式:PFmerge <destkey> <sourcekey> [<sourcekey>...]

localhost:6379> PFadd T1  1 2  2 3 4  #T1统计结果集
(integer) 1
localhost:6379> PFadd T2  3 4 5       #T1统计结果集
(integer) 1
localhost:6379> PFmerge  T1 T2        #将T2结果集内容添加至T1结果集中
OK
localhost:6379> PFCount T1
(integer) 5
注意

Hyperloglog数据类型并不存储元素数据,因此只能统计元素个数而不能统计到底包含了哪些元素~

0X02-06#Bitmap

C/C++用户对此类数据结构再熟悉不过,位图主要使用每1比特位记录信息,例如LeetCode中记录用户是打卡功能就可用位图实现:1 已打卡 0 位打卡。 位图当中只有两种状态0/1,通常操作二进制来记录相应的信息!!

设置位图记录

基本格式:set <key> <offset> <value>

<offset>:偏移量

<value>:值

#周一到周日的打卡记录
localhost:6379>setbit  sign 0 1
(integer)0
localhost:6379>setbit  sign 1 0
(integer)0
localhost:6379>setbit  sign 2 1
(integer)0
localhost:6379>setbit  sign 3 1
(integer)0
localhost:6379>setbit  sign 4 1
(integer)0
localhost:6379>setbit  sign 5 1
(integer)0
localhost:6379>setbit  sign 6 1
(integer)0
获取位图记录

基本格式:getbit <key> <offset>

<offset>: 偏移量

# 记录周一到周日的打卡记录
localhost:6379>setbit  sign 2 1
(integer)0
# 查看周三是否打卡
localhost:6479>getbit sign 2
(integer)1
统计位图信息

基本格式:bitcount <key> [<start> <end>]

# 记录周一到周日的打卡记录
localhost:6379>setbit  sign 0 1
(integer)0
localhost:6379>setbit  sign 1 1
(integer)0
localhost:6379>setbit  sign 2 1
(integer)0
localhost:6379>setbit  sign 3 1
(integer)0
localhost:6379>setbit  sign 4 1
(integer)0
localhost:6379> bitcount sign          #统计一周打卡多少次
(integer) 5

0X03#Redis 事务

Redis单条命令是保证原子性的,但事务并不保证原子性!!

原子性:操作不可被中断,要么全部执行成功要么全部执行失败~

Redis事务的本质更像一组命令的集合,使用multi开始事务后,依次输入命令入队需要依次执行的命令,最后输入exec执行该命令集合!

Redis事务使用的基本方式:

multi #开启事务
      #命令入队
exec  #执行事务
事务正常执行

通常情况下,事务中的命令都顺序且成功地执行:

localhost:6379> multi                      #开启事务
OK
localhost:6379(TX)> lpush L1 1 2 3         #命令入队
QUEUED
localhost:6379(TX)> set k1 v1              
QUEUED
localhost:6379(TX)> sadd Color Red Green
QUEUED
localhost:6379(TX)> exec                  #执行命令集合
1) (integer) 3
2) OK
3) (integer) 2
放弃事务

何时放弃事务?又为何放弃事务?

想必看到这里的兄弟萌一定会想着两个问题。在多线程中同时操作同一个事务(事务开启后往事务中添加命令的状态)时,如果操作命令参数缺失,此时则需要放弃事务避免数据库数据的错误!此外如果存在除零操作也同样需要放弃事务。

编译型异常:

编译型异常通常在命令添加至队列时将报相应的错误信息!

localhost:6379> multi                     #开启事务
OK
localhost:6379(TX)> lpush L1 1 2 3        #线程T1添加命令至队列
QUEUED
localhost:6379(TX)> set k1 v1             #线程T1添加命令至队列
QUEUED
localhost:6379(TX)> getset k1             #线程T2添加具有语法错误的命令至队列
(error) ERR wrong number of arguments for 'getset' command
localhost:6379(TX)> discard               #果断放弃事务避免数据错误

运行时异常:

运行时异常通常在运行时才会返回并显示报错信息,但值得注意的是该命令实行失败并不会影响其他命令的实行。即Redis命令事务并不保证原子性!

localhost:6379> set v1 "v1"              #v1-“v1”
OK
localhost:6379> multi                    #开启事务
OK
localhost:6379(TX)> incr v1              #增加值
QUEUED
localhost:6379(TX)> set k1 v1            #设置键值对
QUEUED
localhost:6379(TX)> exec                 #执行事务
1) (error) ERR value is not an integer or out of range #执行报错
2) OK

疑惑与解答

上述记录了在Redis-cli 中常用的数据结构以及如何使用Redis事务,但相信用过MySQL的用户一定也都用过Navicat这类可视化软件,又何必使用命令行呢?

概述中已经简略介绍过Redis属于NO-SQL数据库,这也意味着没法使用SQL语句操作Redis中的数据,用户需要了解如何使用这些命令。此外更加重要的是Reids给各个编程语言提供的客户端都基本和上述命令行相似,因此学会使用Redis-cli命令行操作数据可以快速上手编程语言相关的API包!

Redis客户端(Redis-cliJedislettuce等)与Redis 服务端基本结构如下:

Redis-cli

0X04#Redis 发布订阅

对象:发布者--频道-- 订阅者

订阅指定的一个或多个频道的信息:SUBSCRIBE channel [channel ...]

将信息发送到指定的频道:PUBLISH channel message

顶退指定的一个或多个频道信息:UNSUBSCRIBE [channel [channel ...]]

订阅一个或多个匹配的频道:PSUBSCRIBE pattern [pattern ...]

退订所有给定模式的频道:PUNSUBSCRIBE [pattern [pattern ...]]

常规应用eg

订阅者:

localhost:6379> subscribe wsharkcoder #订阅一个频道wsharkcder
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "wsharkcoder"
3) (integer) 1
#等待读取推送的消息
1) "message"     #消息
2) "wsharkcoder" #消息对应的频道
3) "hello,world" #消息的内容
1) "message"
2) "wsharkcoder"
3) "hello,redis"
​
​

发布者:

localhost:6379> publish wsharkcoder "hello,world" #发布者发布消息到频道!
(integer) 2
localhost:6379> publish wsharkcoder "hello,redis" #发布则发布消息到频道!
(integer) 2

特殊应用eg:Redis中Key到期提醒

#监听者
localhost:6397> config set notify-keyspace-events Ex
OK
#订阅频道
localhost:6397> psubscribe  __keyevent@2__:expired
#__keyevent@<DB_ORDER>__:expired
#DB_ORDER:数据库序号
#订阅开启之后这个库的所有key过期时间都会被推送过来;
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@2__:expired"
3) (integer) 14) "name"
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "name"
#发布者
localhost:6397[2]> set name  "wsharkcoder" ex 5
OK

0X05#Redis-Java客户端

前面学习Redis数据结构与操作时使用的时Redis内置客户端Redis-cli,但编程语言没办法使用Redis-cli命令行,那编程语言如何操作Redis?

Redis 官网已经解答此问题:

image-20210530214357609

此外官方也提示道,被星号标记的客户端是被推荐到客户端,被笑脸标记的客户端至少已经存在6个月。此外用户也可DIY自己的客户端!!

Java部分被官方推荐客户端有Jedis,lettuceRedisson三种客户端,下面就简略记录一下Jedislettuce的使用与特点。

0X05-1#Jedis

借用Redis官网中对Jedis的描述:A blazingly small and sane Redis Java client

Jedis并没有相应的官方网站,但该项目开源并在Github平台上维护着,那么直接到Github上该项目README中查看线索即可。

Jedis开源项目地址: https://github.com/redis/jedis

如何使用?
导入依赖

想要使用Jedis必须加载Jar包或添加其Maven依赖,jar包可以在其Github仓库自行寻找,Maven项目直接添加依赖即可:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>
使用示例
public static void main(String[] args) {
    //Jedis 建立本地Redis连接
    Jedis jedis = new Jedis("localhost", 6379);
    //验证身份
    jedis.auth("wsharkcoder");
    //测试连接是否成功;连接失败直接返回
    final String PONG = "PONG";
    //测试连接
    if (jedis.ping().equals(PONG)) {
        System.out.println("Redis Connection Success.");
    } else {
        return;
    }
    //flushdb命令-->删除当前数据库内所有键值对
    jedis.flushDB();
    //select <index>命令-->选择第<index>号数据库
    jedis.select(0);
    //set <key> <value>命令
    jedis.set("name", "WSharkCoder");
    //get <key> 命令
    String val = jedis.get("name");
    System.out.println(val);
    //jedis关闭连接
    jedis.close();
}
//运行结果
//Redis Connection Success.
//WSharkCoder

Jedis客户端包的使用方式和Redis-cli的命令行基本一一对应 ,均借助Jedis实例的对应方法即可,其使用方法就不作更多罗列~!

事务使用实例

关于Jedis事务部分的操作与数据的处理略微有所不同,依旧补充相应的实例详细说明如何使用其事务部分:

 public static void main(String[] args) {
     //Jedis 建立本地Redis连接
     Jedis jedis = new Jedis("129.211.66.191", 6379);
     //验证身份
     jedis.auth("wsharkcoder");
     jedis.flushDB();
     JSONObject jsonObject = new JSONObject();
     jsonObject.put("name","wsharkcoder");
     jsonObject.put("age", 20);
     String result = jsonObject.toJSONString();
     jedis.watch("name");
     //开启事务
     Transaction multi = jedis.multi();
     try {
         multi.set("user1", result);
         multi.set("user2", result);
         //代码抛出事务异常,执行失败!
         int i = 1 / 0;
         //执行事务
         multi.exec();
     } catch (Exception e) {
         //放弃事务
         multi.discard();
     }finally {
         System.out.println(jedis.get("user1"));
         System.out.println(jedis.get("user2"));
         //关闭连接
         jedis.close();
     }
 }

本示例redis-client-jedis 仅仅只包含单纯的如何使用Jedis,更加重要的其他内容并没有讨论,例如连接池,Watch以及Lua等,日后进行补充与深入 。关于了解这些高级部分的博客在源码库README部分链接中也有列举,急需使用这部分的兄弟可以去查阅查阅 !

0X05-2#Lettuce

Lettuce官网是这样描述Lettuce的:Lettuce is a scalable client for building non-blocking Reactive applications.

为什么已经了解过Jedis 还要学习Lettuce?

本人第一次了解到Lettuce是在SpringBoot中,在SpringBoot 2.x之后SpringBoot默认的的客户端就由原来的Jedis变为Lettuce 。这也就意味着SpringBoot官方团队认为LettuceJedis更加优秀!

关于网上关于二者的讨论很多,本人感觉最重要的两个优点是:非阻塞IO 线程安全

和上述Jedis一样本模块内仅仅讨论如何使用,关于Lettuce更高阶的部分日后再详细讨论!

如何使用?
导入依赖

普通项目下载Jar包并添加到项目中即可,Maven项目则直接在POM文件中添加一下依赖即可:

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.2.RELEASE</version>
</dependency>
使用示例
    public static void main(String[] args) {
        //1.创建RedisClient实例
        //参数解释:
        //redis://WSharkCoder@localhost:6379/1
        //密码:WSharkCoder 主机IP:localhost 端口号:6379 数据库编码:1号
        RedisClient redisClient = RedisClient.create("redis://WSharkCoder@localhost:6379/1");
        //2.打开Redis独立链接
        StatefulRedisConnection<String,String> connection=redisClient.connect();
        //3.同步执行
        RedisCommands<String, String> commands = connection.sync();
        //4.发送命令与执行命令
        String val=commands.ping();
        System.out.println(val);
        commands.set("K1", "V1");
        val = commands.get("K1");
        System.out.println(val);
        //5.关闭链接
        connection.close();
        //6.关闭客户端实例以释放线程和资源
        redisClient.shutdown();
    }

Lettuce使用相对于Jedis而言复杂很多,官方文档也对其中很多参数做了相当多的定义与规定,而且API也分为同步,异步,响应式三种。此处示例中仅包含最简单的同步方式,与上述Jedis的示例基本一致。

0X05-3#SpringData集成Redis

首先先总结一下上述两部分中咱都聊了个啥?其实本质上只解决了两个问题,Redis常用数据类型和如何操作这些数据类型,其中如何操作数据类型包括命令行方式编程语言调度两部分。 Java框架中是有集成Redis使用,那么下面记录一下SpringBoot中如何快速高效地使用Redis

集成Redis?

SpringBoot参考文档中关于NOSQL数据库使用中介绍道:Spring Data provides additional projects that help you access a variety of NoSQL technologie即SpringBoot直接借助Spring Data项目中关于Redis等NOSQL的实现部分。如果再查找到Spring Data Redis 的文档,就会发现其内部的实现其实是依赖于JedisLettuce的。简而言之,Spring Data RedisJedisLettuce封装集成和统一API,而SpringBoot自动配置Spring Data Redis 达到开箱即用的目的!

image-20210602161314212

如何使用?

SpringBoot项目基本由Maven构建,直接添加依赖即可:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

g

SpringData 集成Redis

SpringBoot操作数据:spring-data jpa jdbc mongdb redis!

SpringData 也是SpringBoot齐名项目!

说明:在SpringBoot2.x之后,原来使用的Jedis被替换成Lettuce?

Jedis:采用直连,多个线程操作的话,是不安全的,如果避免不安全,使用Jedis pool 连接池!BIO模式

lettuce: 采用netty,s实例可以在多个线程中共享,不存在线程不安全的情况! 可以减少线程数据!NIO模式

  • 源码分析:

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")//可自定义redisTemplate来替换这个默认的!
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
          //默认的RedisTemplate没有过多的设置,redis对象都是需要序列化!
          //两个泛型都是Object,Object的类型,我们后使用需要强制转换<String,Object>
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    ​
    @Bean
    @ConditionalOnMissingBean//由于String是redis中最常见的类型,所以说单独提出一个Bean来!
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    
  • 添加依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
  • 配置Redis

    #配置连接Redis—Server 主机IP
    spring.redis.host=localhost
    #配置连接Redis—Server 端口号
    spring.redis.port=6379
    
  • 测试

    @SpringBootTest
    public class RedisTest {
        @Autowired
        private RedisTemplate redisTemplate;
        @Test
        public void Test_01(){
            //redisTemplate 操作不同的数据类型,ops和命令是类似的
            //opsForValue() 操作字符串 类似String
            //opsForList()
            //opsForSet()
            //opsForHash()
            //opsForZSet()
            //opsForGeo()
            //opsForHyperLogLog()
            redisTemplate.opsForValue().set("name", "Wsharkcoder");
    //       除了上方的操作外,常用的方法都可以直接通过RedisTemplate操作,比如事务,和基本CRUD
    //        RedisConnection redisConnection= redisTemplate.getConnectionFactory().getConnection();
    //        redisConnection.flushAll();
    //        redisConnection.flushDb();
            String name = (String) redisTemplate.opsForValue().get("name");
            System.out.println(name);
        }
    }
    

==自定义RedisTemplate==

说明:

  • Redis 存储对象时需要将对象经行序列化!默认采用JDK序列化方式!

为什么需要自定义RedisTemplate?

解释:SpringData-Redis 默认采用的是将JDK序列化方式,而JDK序列化方式引发Redis数据库编码异常问题,即数据在Redis中以乱码的呈现,其次自定义以JSON格式经行序列化更符合企业级要求!!

  • 自定义配置Redis

    @Configuration
    //自定义RedisTemplate
    public class RedisConfig {
    ​
        @Bean(name="diyRedisTemplate")
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
                throws UnknownHostException {
            //方便开发,一般直接使用<String,Object>
            RedisTemplate<String , Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            //JSON序列化配置
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
            //String序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            //所有Key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            //Hash的key也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            //所有Value采用JackSon序列化方式
             template.setValueSerializer(jackson2JsonRedisSerializer);
            //Hash Value序列化方式采用Jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    ​
    }
    
  • 测试自定义RedisTemplate

    @SpringBootTest
    public class RedisTest {
        @Autowired
        @Qualifier(value = "redisTemplate")
        private RedisTemplate redisTemplate;
        @Autowired
        private RedisTemplate diyRedisTemplate;
        @Test
        public void Test_02(){
            User user = new User("方", 20);
    //      报错:
    //        org.springframework.data.redis.serializer.SerializationException:
    //        Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException:
    //        Failed to serialize object using DefaultSerializer;
    //        nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable
    //        payload but received an object of type [cn.wsharkcoder.redis_springboot_demo.entity.User]
    ​
            redisTemplate.opsForValue().set("my_info", user);
    //       报错原因: User类无法进行序列化
    //       解决方法:
    //        public class User
    //        改为:
    //        public class User implements Serializable
            System.out.println(redisTemplate.opsForValue().get("my_info"));
        }
       
        @Test
        public void  Test_03() {
            User user = new User("方", 20);
            diyRedisTemplate.opsForValue().set("my_info", user);
            System.out.println(diyRedisTemplate.opsForValue().get("my_info"));
    ​
        }
    }
    

0X06#Redis配置

Redis 启动的时候,都是通过配置文件来启动!eg: ./redis-server /path/to/redis.conf

配置文件 redis.conf

## Generated by install_server.sh ##
# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

配置文件unit单位对大小写不敏感!

可以引入其他配置文件

################################## INCLUDES #################################### Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

网络配置相关

bind 127.0.0.1 #绑定IP(此处配置表示只能在本地进行连接!)
protected-mode no #保护模式
port 6379 #端口设置

常规配置项

daemonize yes #以守护进程的方式运行,默认是no,我们需要自己开启
pidfile /var/run/redis_6379.pid #如果以后台方式运行,需要自定一个pid文件!
​
​
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
logfile /var/log/redis_6379.log #日志的文件位置名
databases 16 #数据库数目,默认是16个数据库
always-show-logo yes #是否总是显示LOGO 

RDB持久化相关配置

持久化,在规定时间内, 执行多少次操做,则会持久到文件.rdb.aof

redis 是内存数据库,如果没有持久化,那么数据断电即失!

#如果900秒内,如果至少有一个key进行修改,将及时进行持久化
save 900 1
#如果300秒内,如果至少10个key进行修改,将及时进行持久化
save 300 10
#如果60秒内,如果进行10000个key进行修改,将及时进行持久化
save 60 10000
​
stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作
rdbcompression yes #是否压缩rdb文件
rdbchecksum yes #保存rdb文件的时候,进行错误校验
dir /var/lib/redis/6379 #rdb文件保存的目录!

AOF持久化配置

appendonly no #默认不开启AOF模式,默认是使用RDB方式持久化,大部分所有情况下,RDB模式完全够用!
appendfilename "appendonly.aof" #持久化的名字# appendfsync always    #每次修改都会sync,消耗性能
appendfsync everysec    #每秒执行一次sync, 可能会丢失1s的数据!
# appendfsync no        #不执行sync,这个时候操作系统自己同步数据,速度最快!

安全相关配置

可以搜索关键词 SECCURITY

################################## SECURITY #################################### Require clients to issue AUTH <PASSWORD> before processing any other
# commands.  This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
requirepass 123456
​

配置文件中以上部分对应基本的Redis 密码配置信息! (默认情况下,Redis并没有密码的!)

#Redis配置安全信息
localhost:6379> ping
PONG
localhost:6379> config  get requirepass  #获取Redis的密码
1) "requirepass"
2) "wsharkcoder"
localhost:6379> config set requirepass "12345" #设置Redis密码
OK
localhost:6379> config get requirepass #获取Redis 密码
1) "requirepass"
2) "12345"
localhost:6379> config get requirepass  
(error) NOAUTH Authentication required. #命令没有权限!
localhost:6379> auth "123456" #使用密码进行登陆
OK 

限制客户端数量配置

maxclients 10000 #设置能连接上Redis的最大 客户端数量
maxmemory <bytes> #Redis配置最大的内存容量
maxmemory-policy noeviction # 内存达到上限之后的处理策略
1. volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰;
2. volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰;
3. volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰;
4. allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰;
5. allkeys-random:从数据集中任意挑选数据 淘汰;
6. no-enviction(驱逐):禁止驱逐数据。(默认淘汰策略。当redis内存数据达到maxmemory,在该策略下,直接返回OOM错误);
​

0X07#Redis 主从复制

0X07-1#实现主从复制

主库用来经行增删信息操作,从库主要用来进行读取操作!通过单台主机和多台从机形成Redis集群,减缓服务器压力!·

  • 环境配置(构建一主二从 Redis集群)

    只需要配置从 库,不配置主库!

    查看单Redis主从复制信息:

     localhost:6379> info replication #查看当前库信息(主从复制信息;)
    # Replication
    role:master             #角色(主库)
    connected_slaves:0      #从机个数
    master_repl_offset:0
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
    localhost:6379> 
    

    复制三份 Redis.conf 文件 ,修改基本配置信息 :

    • 端口
    • pid 名称
    • log名称
    • DUMP文件名称

    主机修改配置 redis-6379-port.conf

    logfile "6379.log"  #日志文件名称
    dbfilename dump-6379.rdb #RDB持久文件名 
    

    从机—01修改配置 redis-6380-port.conf

    port 6380 #配置从机-01端口
    pidfile /var/run/redis_6380.pid #配置PID
    logfile "6380.log" #日志文件名称
    dbfilename dump-6380.rdb #RDB持久文件名 
    masterauth wsharkcoder  #主机认证密码
    

    从机-02修改配置redis-6381-port.conf

    port 6381 #配置从机-01端口
    pidfile /var/run/redis_6381.pid #配置PID
    logfile "6381.log" #日志文件名称
    dbfilename dump-6381.rdb #RDB持久文件名 
    masterauth wsharkcoder  #主机认证密码
    

    通过配置文件分别开启三个Redis服务

    [root@VM_0_8_centos src]# redis-server ../redis-79-port.conf  #开启组
    [root@VM_0_8_centos src]# redis-server ../redis-80-port.conf
    [root@VM_0_8_centos src]# redis-server ../redis-81-port.conf
    [root@VM_0_8_centos src]# ps -ef | grep redis-server          #查看Redis进程信息
    root      1933     1  0 20:01 ?        00:00:00 redis-server 127.0.0.1:6380
    root      1982     1  0 20:02 ?        00:00:00 redis-server 127.0.0.1:6381
    root      2900 18572  0 20:07 pts/0    00:00:00 grep --color=auto redis-server
    root     29958     1  0 19:33 ?        00:00:01 redis-server 127.0.0.1:6379
    
  • 命令行配置从机关系

    默认情况下,每台Redis服务器都是主节点; 一般情况下,只需要配置从机就好了!

    slaveof <IP> <Port>

    将本机配置为<IP><Port>主机的从机!

    #[6380从机]
    ​
    [root@VM_0_8_centos redis-3.2.8]# redis-cli -h localhost -p 6380 -a wsharkcoder 
    localhost:6380> slaveof localhost 6379   #将 6380-port Redis从机配置为 6379-port Redis主机的从机 
    OK
    localhost:6380> info replication #查看当前库信息
    # Replication
    role:slave                       #角色(从机)
    master_host:localhost            #主机IP(localhost)
    master_port:6379                 #主机端口(6379)
    master_link_status:down
    master_last_io_seconds_ago:-1
    master_sync_in_progress:0
    slave_repl_offset:1
    master_link_down_since_seconds:1602073077
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_repl_offset:0
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0#[6381从机]
    ​
    [root@VM_0_8_centos redis-3.2.8]# redis-cli -h localhost -p 6381 -a wsharkcoder
    localhost:6381> slaveof localhost 6379
    OK
    localhost:6381> info replication
    # Replication
    role:slave
    master_host:localhost
    master_port:6379
    master_link_status:down
    master_last_io_seconds_ago:-1
    master_sync_in_progress:0
    slave_repl_offset:1
    master_link_down_since_seconds:1602073848
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_repl_offset:0
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0#[6379 主机]
    [root@VM_0_8_centos redis-3.2.8]# redis-cli -h localhost -p 6381 -a wsharkcoder
    localhost:6379> info replication  
    # Replication
    role:master                 
    connected_slaves:2                                          #从机数目
    slave0:ip=127.0.0.1,port=6381,state=online,offset=155,lag=1 #从机-01 基本信息
    slave1:ip=127.0.0.1,port=6380,state=online,offset=155,lag=1 #从机-02 基本信息
    master_repl_offset:155
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:2
    repl_backlog_histlen:154
    
  • 修改配置文件配置主从关系

    slaveof <masterip> <masterport>

    配置主机IP&端口

    slaveof  localhost  6379
    
0X07-2#主从复制细节
  • 主从(Redis库)读写测试

    主机可以写, 从机不能写职能读!主机中所有信息和数据,都会自动被从机保存!==

    # [6379 主机 可写&可读]
    [root@VM_0_8_centos redis-3.2.8]# redis-cli -h localhost -p 6379 -a wsharkcoder
    localhost:6379> set k1 v1
    OK
    localhost:6379> get k1
    "v1"
    # [6380 从机 不可写&可读]
    [root@VM_0_8_centos src]# redis-cli -h localhost -p 6380 -a wsharkcoder
    localhost:6380> set k2 v2
    (error) READONLY You can't write against a read only slave.
    localhost:6380> get k1
    "v1"
  • 模拟主(Redis库)宕机

    测试:主机断开连接 ,从机依旧连接到主机,但是没有写操作,这时候,如果主机重新连接回来,从机依旧可以直接获取到主机写的信息!

    #模拟6379主机宕机
    [root@VM_0_8_centos redis-3.2.8]# redis-cli -h localhost -p 6379 -a wsharkcoder
    localhost:6379> shutdown            #主机宕机
    not connected> exit#6380从机获取键值对
    [root@VM_0_8_centos src]# redis-cli -h localhost -p 6380 -a wsharkcoder
    localhost:6380> get k2
    (nil)
    ​
    #模拟6379主机恢复连接
    [root@VM_0_8_centos src]# redis-server ../redis-79-port.conf     
    [root@VM_0_8_centos src]# redis-cli -h localhost -p 6379 -a wsharkcoder
    localhost:6379> set k2 v2
    OK
    #6380从机获取键值对====>(主机重新连接回来,从机依旧可以直接获取到主机写的信息)
    [root@VM_0_8_centos src]# redis-cli -h localhost -p 6380 -a wsharkcoder
    localhost:6380> get k2
    "v2"
    
0X07-3#复制原理
  • Slave 启动成功连接到Master后会发送 Sync 同步命令.

  • Master 收到Sync 命令后,启动后台的存盘进程,同时收集所有接受到的用于修改数据集命令,在后台进程执行完毕之后,Master将传送整个数据文件到Slave,并完成一次完全同步.

    全复制: Slave服务在接收到数据库文件后,将其存盘并加载到内存中.

    增量复制: Master继续将新的所有收集到的修改命令依次传递个Slave ,完成同步.

    只要重新连接Master, 一次完全同步(全量复制)将被执行。

参考资料

【狂神说Java】Redis最新超详细版教程通俗易懂-----B站狂神-Redis篇章

Redis Architecture----Client/Server结构图参考