Redis

Redis参考资料

官方资料

Redis官网

Redis中文官网

Redis官方文档

下载:
其他资料链接

Java 全栈知识体系--Redis教程

Redis教程 w3cschool

redis设置密码以及jedisPool设置密码(模板)

Redis的两种持久化RDB和AOF

redis 基本指令演示

Redis 安装

大数据时代的3V:主要是描述问题的

  • 海量Volume
  • 多样Variety
  • 实时Velocity

大数据时代的3高:主要是对程序的要求

  • 高并发
  • 高可拓
  • 高性能

windows

image-20220508111952052

image-20220508112335530

Linux

# 压缩包在 /usr/local
# 解压
tar -zxvf redis-6.2.7.tar.gz

# 进入到 redis-6.2.7 文件件
# 基本的环境安装
yum install gcc-c++
	# gcc -v
make
	# make 
	# make install

image-20220508120640171

# 进入 local/bin
cd local/bin
ll

image-20220508121108834

# 新建 usrconf 文件夹
mkdir usrconfig
# 复制 redis 配置文件
cp /usr/local/redis-6.2.7/redis.conf usrconfig
# 以后使用 bin/usrconfig 配置文件(local/redis-6.2.7/redis.conf 为原始文件)

# 修改配置文件
cd usrconfig
vim redis.conf
# daemonize no --> yes

# 启动
cd ../
redis-server usrconf/redis.conf


# 测试
redis-cli -p 6379
ping
>pong
set name Kite
>OK
get name
>"kite"
keys *
>"kite"

# 查看 redis 进程是否开启
ps -ef|grep redis
# 开启服务
[root@kite bin]# redis-server usrconf/redis.conf
# 查看进程
[root@kite bin]# ps -ef|grep redis
root     20743     1  0 12:23 ?        00:00:00 redis-server 127.0.0.1:6379
root     21775 21156  0 12:32 pts/0    00:00:00 grep --color=auto redis
# 客户端测试
[root@kite bin]# redis-cli -p 6379
127.0.0.1:6379> .....
# 退出
127.0.0.1:6379> shutdown
not connected> exit
# 再次查看进程
[root@kite bin]# ps -ef|grep redis
root     22405 21156  0 12:38 pts/0    00:00:00 grep --color=auto redis
测试性能
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 每秒处理请求 7741.14 次请求
# 100000请求在 2.01秒完成
# 100 个并发客户端
# 每次写入 3 个字符
# 只有一台服务器来处理这些请求,单机性能

# 整体上每秒处理 49875.31 个请求

image-20220508150118748

image-20220508150653969

Redis 基础知识

redis默认有16个数据库,默认使用第 0 个数据库,可以使用 select n 来切换

Redis是单线程的

Redis是基于内存操作的,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,可以使用单线程来实现,就使用单线程

Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-vale的Memecache差

Redis为什么单线程还这么快?

误区1:高性能的服务器一定是多线程的

误区2:多线程(CPU上下文会切换!)一定比单线程效率高

CPU>内存>硬盘

redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换是耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上,在内存情况下,这个就是最佳的方案!

五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes),列表(lists),集合(sets), 有序集合(sorted sets)与范围查询,bitmaps, hyperloglogs和 地理空间(geospatial)索引半径查询。 Redis 内置了复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key 的基本命令

# 数据量
dbsize
# set 
set age 15
# get
get age
# 模糊查找
keys *
keys a*

# 删除
del age
move age 1
flushdb
flushall

# 判断是否存在
exists age
# 设置过期时间(秒)
expire age 10
# 查看剩余过期时间
ttl name 
# 查看类型
type age

String

String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。

  • 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
  • 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
  • session:常见方案spring session + redis实现session共享
APPENDGETBITINCRBYFLOATSETSTRLEN
BITCOUNTGETRANGEMGETSETBIT
BITOPGETSETMSETSETEX
DECRINCRMSETNXSETNX
DECRBYINCRBYPSETEXSETRANGE
##########################################
# incr	incrby
# decr	dexrby

127.0.0.1:6379> incr num
(integer) 4
127.0.0.1:6379> get  num
"4"
127.0.0.1:6379> get  num
"4"
127.0.0.1:6379> decr num
(integer) 3
127.0.0.1:6379> decrby num 10
(integer) -7
127.0.0.1:6379> incrby num 20
(integer) 13

###########################################
# get getrange

127.0.0.1:6379> get str
"abc123"
127.0.0.1:6379> getrange str 1 4
"bc12"
127.0.0.1:6379> getrange str 1 -1
"bc123"
127.0.0.1:6379> getrange str 0 -1
"abc123"

############################################
# set	setrange

127.0.0.1:6379> set key1 aaabbbccc
OK
127.0.0.1:6379> setrange key1 3 ddd
(integer) 9
127.0.0.1:6379> get key1
"aaadddccc"

############################################
# setex (set with expire)
# setnx (set if not exists)	# 分布式锁中常使用

127.0.0.1:6379> setex str 10 abc
OK
127.0.0.1:6379> get str
"abc"
127.0.0.1:6379> ttl str
(integer) -2
127.0.0.1:6379> get str
(nil)
127.0.0.1:6379> setnx str abc
(integer) 1
127.0.0.1:6379> get str
"abc"

############################################
# mset msetnx
# mget 
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> mget k1 k3 k2 
1) "v1"
2) "v3"
3) "v2"
127.0.0.1:6379> mget k1 k1 k1
1) "v1"
2) "v1"
3) "v1"

127.0.0.1:6379> msetnx k1 v11 k2 v22
(integer) 0
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> 

############################################
# 设置多属性

127.0.0.1:6379> set user:1 {name:Kite,age:15}	# key 为 user:1
OK
127.0.0.1:6379> mset user:2:name Lee user:2:age 25 user:3:name Alice user:3:age 8	# key 为 user:2:(age)
OK
127.0.0.1:6379> keys *
1) "user:2:name"
2) "user:3:name"
3) "user:1"
4) "user:2:age"
5) "user:3:age"
127.0.0.1:6379> get user:1
"{name:Kite,age:15}"
127.0.0.1:6379> get user:2
(nil)
127.0.0.1:6379> get user:2:name
"Lee"
127.0.0.1:6379> mget user:2:name user:2:age
1) "Lee"
2) "25"
127.0.0.1:6379> mget user:1
1) "{name:Kite,age:15}"
127.0.0.1:6379> mget user:1:name
1) (nil)

############################################
# getset

127.0.0.1:6379> get str
(nil)
127.0.0.1:6379> getset str abc
(nil)
127.0.0.1:6379> getset str 123
"abc"
127.0.0.1:6379> get str
"123"

List

双端链表

  • 微博TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。
  • 消息队列
BLPOPLLENLREMRPUSH
BRPOPLPOPLSETRPUSHX
BRPOPLPUSHLPUSHLTRIM
LINDEXLPUSHXRPOP
LINSERTLRANGERPOPLPUSH
# lpush a 
# lpush a b c
# rpush a 
# rpush a b c

# Lrange list 0 -1

# lpop list 	
# lpop list 2
# rpop list
# rpop list 2

# lindex list 0
# lindex list 1
# llen list
# rpoplpush list list2
# lset list 0 a

# linsert list before a 123
# linsert list after a 123

127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> keys * 
1) "list"
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush list  four 
(integer) 4
127.0.0.1:6379> keys * 
1) "list"
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop list
"three"
127.0.0.1:6379> rpop list
"four"
127.0.0.1:6379> lpop list 2
1) "two"
2) "one"
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> lpush list one two three
(integer) 3
127.0.0.1:6379> keys *
1) "list"
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lindex list 0
"three"
127.0.0.1:6379> lindex list 1
"two"
127.0.0.1:6379> llen list
(integer) 3

127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "two"
4) "one"
127.0.0.1:6379> rpoplpush list list2
"one"
127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "two"
127.0.0.1:6379> Lrange list2 0 -1
1) "one"

127.0.0.1:6379> lset list 0 a
(error) ERR no such key
127.0.0.1:6379> lpush list a
(integer) 1
127.0.0.1:6379> lset list 0 b
OK
127.0.0.1:6379> lset list 1 b
(error) ERR index out of range


127.0.0.1:6379> Lrange list 0 -1
1) "d"
2) "c"
3) "b"
127.0.0.1:6379> linsert list before c before
(integer) 4
127.0.0.1:6379> linsert list after c after
(integer) 5
127.0.0.1:6379> Lrange list 0 -1
1) "d"
2) "before"
3) "c"
4) "after"
5) "b"

Set

无序不重复

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

  • 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
  • 点赞,或点踩,收藏等,可以放到set中实现
SRANDMEMBERSINTERSTORESADD
SREMSISMEMBERSCARD
SUNIONSMEMBERSSDIFF
SUNIONSTORESMOVESDIFFSTORE
SSCANSPOPSINTER
# sadd
# scard myset
# srem myset hello
# smembers myset
# srandmember myset 1	# 1 为 1 个
# smove myset myset2 redis
# sdiff myset1 myset2
# sinter myset1 myset2
# sunion myset1 myset2

127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset "redis"
(integer) 1
127.0.0.1:6379> sadd myset "Kite"
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> srem myset hello
(integer) 1
127.0.0.1:6379> smembers myset
1) "Kite"
2) "redis"
127.0.0.1:6379> srandmember myset 1
1) "redis"
127.0.0.1:6379> srandmember myset 1
1) "Kite"
127.0.0.1:6379> smove myset myset2 redis
(integer) 1
127.0.0.1:6379> smembers myset
1) "Kite"
127.0.0.1:6379> smembers myset2
1) "redis"
127.0.0.1:6379> 

127.0.0.1:6379> sadd myset1 redis Kite Lee
(integer) 3
127.0.0.1:6379> sadd myset2 redis2 Kite Lee
(integer) 3
127.0.0.1:6379> sdiff myset1 myset2
1) "redis"
127.0.0.1:6379> sinter myset1 myset2
1) "Lee"
2) "Kite"
127.0.0.1:6379> sunion myset1 myset2
1) "Kite"
2) "Lee"
3) "redis2"
4) "redis"

Hash

适合用于存储对象

hash变更的数据 user name age,尤其是是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储!

  • 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等
HDELHINCRBYFLOATHSET
HEXISTSHKEYSHSETNX
HGETHLENHVALS
HGETALLHMGETHSCAN
HINCRBYHMSET
# hset hash1 field1 value
# hget hash1 field1
# hmset hash2 name Kite age 15 gender man
# hmget hash2 name age gender
# hdel hash2 name
# hgetall hash2
# hlen hash2
# hexists hash2 name
# hkeys hash2
# hvals hash2
# hincrby hash2 age 2
# 无 hdecrby hash2 age 5

127.0.0.1:6379> hset hash1 field1 Kite
(integer) 1
127.0.0.1:6379> keys *
1) "hash1"
127.0.0.1:6379> hget hash1 field1
"Kite"
127.0.0.1:6379> hmset hash2 name Kite age 15 gender man
OK
127.0.0.1:6379> hmget hash2 name age gender
1) "Kite"
2) "15"
3) "man"
127.0.0.1:6379> hdel hash2 name
(integer) 1
127.0.0.1:6379> hgetall hash2
1) "age"
2) "15"
3) "gender"
4) "man"
127.0.0.1:6379> hlen hash2
(integer) 2

127.0.0.1:6379> hexists hash2 name
(integer) 0
127.0.0.1:6379> hexists hash2 age
(integer) 1
127.0.0.1:6379> hkeys hash2
1) "age"
2) "gender"
127.0.0.1:6379> hvals hash2
1) "15"
2) "man"

127.0.0.1:6379> hincrby hash2 age 2
(integer) 17
127.0.0.1:6379> hdecrby hash2 age 5
(error) ERR unknown command `hdecrby`, with args beginning with: `hash2`, `age`, `5`, 
127.0.0.1:6379> hsetnx hash2 age 18
(integer) 0
127.0.0.1:6379> hsetnx hash2 name Kite
(integer) 1

SortedSet

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

压缩列表(ziplist): ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。它能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关

跳跃表(zSkiplist): 跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是O(log(n))。

  • 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
ZADDZRANGEBYSCOREZREVRANGEZINTERSTORE
ZCARDZRANKZREVRANGEBYSCOREZSCAN
ZCOUNTZREMZREVRANK
ZINCRBYZREMRANGEBYRANKZSCORE
ZRANGEZREMRANGEBYSCOREZUNIONSTORE
# zadd myset 1 one
# zadd myset 3 three 4 four
# zadd myset -5 "negative five"

# zrange myset 0 -1			# 正序
# zrevrange myset 0 -1		# 倒序

# zrangebyscore myset -3 3				# 正序 -3 到 3 k
# zrangebyscore myset -3 3 withscores	# 正序 -3 到 3 v & k

# zrevrangebyscore myset 3 -3				# 倒序 3 到 -3 k
# zrevrangebyscore myset 3 -3  withscores	# 倒序 3 到 -3 v & k


# zcount myset 0 5



127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three 4 four
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> zrevrange myset 0 -1
1) "four"
2) "three"
3) "two"
4) "one"

127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 5 five
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zadd myset -5 "negative five"
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "negative five"
2) "one"
3) "three"
4) "five"
127.0.0.1:6379> zrangebyscore myset -3 3
1) "one"
2) "three"
127.0.0.1:6379> zrangebyscore myset -3 3 withscores
1) "one"
2) "1"
3) "three"
4) "3"
127.0.0.1:6379> zrevrangebyscore myset 3 -3  
1) "three"
2) "one"
127.0.0.1:6379> zrevrangebyscore myset 3 -3  withscores
1) "three"
2) "3"
3) "one"
4) "1"

127.0.0.1:6379> zrange myset 0 -1 withscores
1) "negative five"
2) "-5"
3) "one"
4) "1"
5) "three"
6) "3"
7) "five"
8) "5"
127.0.0.1:6379> zcount myset 0 5
(integer) 3

三种特殊数据类型

Geospatial

GEOADD

GEODIST

GEOHASH

GEOPOS

GEORADIUS

GEORADIUSBYMEMBER

# geoadd china:city 108.96 34.26 xian
# geopos china:city xian
# geodist china:city xian hangzhou
# geodist china:city xian hangzhou km
# georadius china:city 115 35 1000 km
# georadiusbymember china:city xian 1000 km
# geohash china:city xian shenzhen
# zrange china:city 0 -1 withscores

127.0.0.1:6379> geoadd china:city 144.05 22.52 shenzhen 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 3
127.0.0.1:6379> geopos china:city xian
1) 1) "108.96000176668167114"
   2) "34.25999964418929977"
127.0.0.1:6379> geodist china:city xian hangzhou
"1143629.4970"
127.0.0.1:6379> geodist china:city xian hangzhou km
"1143.6295"
127.0.0.1:6379> georadius china:city 115 35 1000 km
1) "hangzhou"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city xian 1000 km
1) "xian"
127.0.0.1:6379> geohash china:city xian shenzhen
1) "wqj6zky6bn0"
2) "xhn2jgwh820"
127.0.0.1:6379> zrange china:city 0 -1 withscores
1) "xian"
2) "4040115445396757"
3) "hangzhou"
4) "4054133997236782"
5) "shenzhen"
6) "4154606886655324"

Hyperloglogs

这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等。

如果允许容错,那么一定可以使用Hyperloglog 如果不允许容错,就使用set或者自己的数据类型

# pfadd mykey1 a  b c d e
# pfadd mykey2 c d e f g
# pfcount mykey1
# pfmerge mykey3 mykey1 mykey2


127.0.0.1:6379> pfadd mykey1 a  b c d e
(integer) 1
127.0.0.1:6379> pfcount mykey1
(integer) 5
127.0.0.1:6379> pfadd mykey2 c d e f g
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey1 mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 7

Bitmaps

  • 统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps
  • 如果存储一年的打卡状态需要多少内存呢? 365 天 = 365 bit 1字节 = 8bit 46 个字节左右!
# setbit sign 0 1
# getbit sign 2
# bitcount sign


127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0

127.0.0.1:6379> getbit sign 2
(integer) 0
127.0.0.1:6379> getbit sign 5
(integer) 1

127.0.0.1:6379> bitcount sign
(integer) 4

事务

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!

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

  • 开启事务()
  • 命令入队()
  • 执行事务()
# multi
# ...
# exec 
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
127.0.0.1:6379(TX)> set key3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) OK

# multi
# ...
# discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get key1
(nil)


# multi
# ...
# 编译异常
# ...
# exec 
# 不能执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set a 1
QUEUED
127.0.0.1:6379(TX)> set b 2
QUEUED
127.0.0.1:6379(TX)> getset a
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> get b
QUEUED
127.0.0.1:6379(TX)> set a 5
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.

# multi
# ...
# 运行异常
# ...
# exec 
# 异常所在行不能执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set a "a"
QUEUED
127.0.0.1:6379(TX)> incr a
QUEUED
127.0.0.1:6379(TX)> set b 2
QUEUED
127.0.0.1:6379(TX)> set c 1/0
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
127.0.0.1:6379> get c
"1/0"

乐观锁

127.0.0.1:6379> set A 100
OK
127.0.0.1:6379> set B 0
OK
127.0.0.1:6379> watch A
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set A 80
QUEUED
127.0.0.1:6379(TX)> set B 20
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
127.0.0.1:6379> get A
"80"
127.0.0.1:6379> get B
"20"
127.0.0.1:6379> get money
"1000"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
# # # # # #
127.0.0.1:6379> set money 500
OK
# # # # # #
127.0.0.1:6379(TX)> decrby money 300
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> get money
"500"

image-20220509114759171

事务失败, 先解锁,再加锁

image-20220509120449942

Jedis

远程 linux redis 连接

vim redis.conf

  • protected-mode no
  • #bind 127.0.0.1
import redis.clients.jedis.Jedis;

public class TestPing {

    public static void main(String[] args) {

        Jedis jedis = new Jedis("101.201.100.130", 6379);
        System.out.println(jedis.ping());
        jedis.close();

    }
}
package com.lee;

import redis.clients.jedis.Jedis;

import java.util.Set;

public class TestString {

    public static void main(String[] args) {

        Jedis jedis = new Jedis("101.201.100.130", 6379);

        System.out.println("清空数据:" + jedis.flushDB());
        System.out.println("判断key<username>是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','Kite'>的键值对:"+jedis.set("username","Kite"));
        System.out.println("新增<'password','password'>的键值对:"+jedis.set("password","password"));
        System.out.print("系统中所有的键如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
        System.out.println("删除键password:"+jedis.del("password"));
        System.out.println("判断键password是否存在:"+jedis.exists("password"));
        System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
        System.out.println("随机返回key空间的一个:"+jedis.randomKey());
        System.out.println("重命名key:"+jedis.rename("username","name"));
        System.out.println("取出改后的name:"+jedis.get("name"));
        System.out.println("按索引查询:"+jedis.select(0));
        System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
        System.out.println("返回当前数据库中key的数目:" + jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:"+ jedis.flushAll());


        jedis.close();
    }
}
package com.lee;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.args.ListPosition;

import java.util.List;

public class TestList {

    public static void main(String[] args) {
        /*
            lpush a
            lpush a b c
            rpush a
            rpush a b c

            Lrange list 0 -1

            lpop list
            lpop list 2
            rpop list
            rpop list 2

            lindex list 0
            lindex list 1
            llen list
            rpoplpush list list2
            lset list 0 a

            linsert list before a 123
            linsert list after a 123
         */

        Jedis jedis = new Jedis("101.201.100.130", 6379);

        jedis.flushDB();
        jedis.lpush("list", "a", "b", "c");
        jedis.rpush("list", "z", "y", "x");

        List<String> list = jedis.lrange("list", 0, -1);
        for (String l : list) {
            System.out.print(l + "  ");
        }
        // c  b  a  z  y  x
        System.out.println();
        System.out.println(jedis.lpop("list")); // c
        System.out.println(jedis.rpop("list")); // x

        jedis.linsert("list", ListPosition.BEFORE, "z", "s");
        jedis.linsert("list", ListPosition.AFTER, "a", "m");

        System.out.println(jedis.lindex("list", 2));    // m
        System.out.println(jedis.lindex("list", 3));    // s
        System.out.println(jedis.lindex("list", 4));    // z

        System.out.println(jedis.llen("list"));     // 6
        System.out.println(jedis.rpoplpush("list", "list2"));   // y
        System.out.println(jedis.llen("list"));     // 5

        System.out.println(jedis.exists("list2"));  // true

        jedis.close();

    }
}

SpringBoot集成Redis

新建 spring 项目 删除选中项

image-20220509193843871

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

SpringData 是和SpringBoot齐名的项目

在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce

  • jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池【与BlO相似】
  • lettuce:采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!【NIO相似】

源码分析

操作redis

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

image-20220509205611990

  • SpringBoot所有的配置类,都有一个自动配置类 RedisAutoConfiguration
  • 自动配置类都会绑定一个properties 配置文件 RedisProperties

RedisAutoConfiguration

public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(name = {"redisTemplate"})	// 可以自定义一个 redisTemplate 来替换这个
    
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 默认的redisTemplate没有过多的设置,redis 对象都需要序列化
       	// 两个泛型都是 <Object, Object> 的类型,后来使用的时候要强制转换 <String,Object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean	// 由于 String 是 redis 中最常用的类型,所以单独提取出一个 
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

测试

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        // redisTemplate.opsForValue() 操作字符串 类似 String
        // redisTemplate.opsForList() 类似 jedis List
        // redisTemplate.opsForSet()
        // redisTemplate.opsForZSet()
        // redisTemplate.opsForHash()
        // ...
        redisTemplate.opsForValue().set("myKey", "二木");
        System.out.println(redisTemplate.opsForValue().get("myKey"));
    }
}

RedisTemplate

新建 实体类 User

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User /*implements Serializable*/ { // 当前版本已经不需要手动进行序列化

    private String name;
    private int age;

}
@Test
public void test() throws JsonProcessingException {

    User user = new User("Kite", 15);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user", jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user"));    // {"name":"Kite","age":15}

}

序列化

复制 RedisTemplate 修改为自定义类型的

com.lee.config

RedisConfig(模板)

package com.lee.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    // 编写自定义 redisTemplate

    @Bean
    @SuppressWarnings("unchecked")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = 
            new Jackson2JsonRedisSerializer(Object.class);
        
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 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;
    }
}

linux 在使用自定义redisTemplate之前, key 是乱码,使用之后恢复正常

127.0.0.1:6379> keys *
1) "backup4"
2) "backup3"
3) "backup1"
4) "backup2"
127.0.0.1:6379> keys
(error) ERR wrong number of arguments for 'keys' command
127.0.0.1:6379> keys *
1) "backup3"
2) "backup4"
3) "\xac\xed\x00\x05t\x00\x05myKey"
4) "backup2"
5) "\xac\xed\x00\x05t\x00\x04user"
6) "backup1"
127.0.0.1:6379> keys *
1) "backup3"
2) "backup1"
3) "user"
4) "backup2"

RedisUtils(模板)

package com.lee.utils;

import org.springframework.data.redis.core.RedisTemplate;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类,使用之前请确保RedisTemplate成功注入
 *
 * @author ye17186
 * @version 2019/2/22 10:48
 */

public class RedisUtils {


    public RedisUtils() {
    }

    @SuppressWarnings("unchecked")
    private static RedisTemplate<String, Object> redisTemplate = (RedisTemplate<String, Object>) SpringUtils.getBean("redisTemplate", RedisTemplate.class);

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public static boolean expire(final String key, final long timeout) {

        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public static boolean expire(final String key, final long timeout, final TimeUnit unit) {

        Boolean ret = redisTemplate.expire(key, timeout, unit);
        return ret != null && ret;
    }

    /**
     * 删除单个key
     *
     * @param key 键
     * @return true=删除成功;false=删除失败
     */
    public static boolean del(final String key) {

        Boolean ret = redisTemplate.delete(key);
        return ret != null && ret;
    }

    /**
     * 删除多个key
     *
     * @param keys 键集合
     * @return 成功删除的个数
     */
    public static long del(final Collection<String> keys) {

        Long ret = redisTemplate.delete(keys);
        return ret == null ? 0 : ret;
    }

    /**
     * 存入普通对象
     *
     * @param key Redis键
     * @param value 值
     */
    public static void set(final String key, final Object value) {

        redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
    }

    // 存储普通对象操作

    /**
     * 存入普通对象
     *
     * @param key 键
     * @param value 值
     * @param timeout 有效期,单位秒
     */
    public static void set(final String key, final Object value, final long timeout) {

        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 获取普通对象
     *
     * @param key 键
     * @return 对象
     */
    public static Object get(final String key) {

        return redisTemplate.opsForValue().get(key);
    }

    // 存储Hash操作

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public static void hPut(final String key, final String hKey, final Object value) {

        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 往Hash中存入多个数据
     *
     * @param key Redis键
     * @param values Hash键值对
     */
    public static void hPutAll(final String key, final Map<String, Object> values) {

        redisTemplate.opsForHash().putAll(key, values);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public static Object hGet(final String key, final String hKey) {

        return redisTemplate.opsForHash().get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public static List<Object> hMultiGet(final String key, final Collection<Object> hKeys) {

        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    // 存储Set相关操作

    /**
     * 往Set中存入数据
     *
     * @param key Redis键
     * @param values 值
     * @return 存入的个数
     */
    public static long sSet(final String key, final Object... values) {
        Long count = redisTemplate.opsForSet().add(key, values);
        return count == null ? 0 : count;
    }

    /**
     * 删除Set中的数据
     *
     * @param key Redis键
     * @param values 值
     * @return 移除的个数
     */
    public static long sDel(final String key, final Object... values) {
        Long count = redisTemplate.opsForSet().remove(key, values);
        return count == null ? 0 : count;
    }

    // 存储List相关操作

    /**
     * 往List中存入数据
     *
     * @param key Redis键
     * @param value 数据
     * @return 存入的个数
     */
    public static long lPush(final String key, final Object value) {
        Long count = redisTemplate.opsForList().rightPush(key, value);
        return count == null ? 0 : count;
    }

    /**
     * 往List中存入多个数据
     *
     * @param key Redis键
     * @param values 多个数据
     * @return 存入的个数
     */
    public static long lPushAll(final String key, final Collection<Object> values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        return count == null ? 0 : count;
    }

    /**
     * 往List中存入多个数据
     *
     * @param key Redis键
     * @param values 多个数据
     * @return 存入的个数
     */
    public static long lPushAll(final String key, final Object... values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        return count == null ? 0 : count;
    }

    /**
     * 从List中获取begin到end之间的元素
     *
     * @param key Redis键
     * @param start 开始位置
     * @param end 结束位置(start=0,end=-1表示获取全部元素)
     * @return List对象
     */
    public static List<Object> lGet(final String key, final int start, final int end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
}

springUtils(模板)

package com.lee.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * Spring 工具类
 *
 * @author zx
 * @date 2011-10-13
 */

@Component("springUtils")
public class SpringUtils implements ApplicationContextAware {


    private static ApplicationContext applicationContext;

    public SpringUtils() {

    }
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext=context;
    }

    /**
     * 根据beanName 获取 spring bean
     * @param beanName
     * @param redisTemplateClass
     * @return Object
     */
    public static Object getBean(String beanName, Class<RedisTemplate> redisTemplateClass){
        if(beanName==null)return null;
        return applicationContext.getBean(beanName);
    }
    /**
     * 根据bean type 获取springBean
     * @param clazz
     * @return
     */
    public static Object getBeanByType(Class clazz){
        return applicationContext.getBean(clazz);
    }

    /**
     * 获取 Spring applicationContext
     * @return
     */
    public static ApplicationContext getContext() {
        return applicationContext;
    }


}

代码来源 CSDN

@Test
public void testUtils() {

    RedisUtils.set("a","1234");
    System.out.println(RedisUtils.get("a"));

}

Redis 配置文件

# 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.


# bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses
# bind 127.0.0.1 ::1              # listens on loopback IPv4 and IPv6
# bind * -::*                     # like the default, all available interfaces

# 一般配置
# bind 127.0.0.1 -::1
protected-mode no	#(defalt yes)
port 6379

####################################################################################

daemonize yes	# (defalt no)


# 如果以后台的方式运行,就需要指定一个 pid 文件
pidfile /var/run/redis_6379.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

# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""		# 生成的日志文件位置及文件名

# 数据库的数量
databases 16


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.7)
# 是否显示 logo
always-show-logo yes

#####################################################################################

# 如果900s内,如果至少有一个1key进行了修改,就进行持久化操作
save 900 1
# 如果300s内,如果至少10key进行了修改,就进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,就进行持久化操作
save 60 10000

stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作!

rdbcompression yes #是否压缩rdb 文件,需要消耗一些cpu资源!

rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验

dir ./	# rdb 文件保存目录

#####################################################################################
# REPLICATION 复制 后面会总结 


#####################################################################################

# security

requirepass 123456	# 密码设置见下方


#####################################################################################

maxclients 10000
# 设置能连接上redis的最大客户端的数量

maxmemory <bytes>	# redis 配置最大的内存容量

maxmemory-policy noeviction 	# 内存到达上限之后的处理策略

volatile-lru:	# 只对设置了过期时间的key进行LRu(默认值)
a11keys-lru:	# 删除1ru算法的key
volatile-random	 # 随机删除即将过期key
a11keys-random 	# 随机删除
volatile-ttl 	# 删除即将过期的
noeviction 		# 永不过期,返回错误

#####################################################################################
# APPEND ONLY MODE AOF
appendonly no	# 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分情况下,rdb完全够用
appendfilename "appendonly.aof" # 持久化的文件名

# appendfsync always	# 每次修改都会同步,消耗性能
appendfsync everysec	# 每秒同步一次,可能会丢失这 1 秒的数据
# appendfsync no		# 不执行同步操作系统自己同步,速度最快

登录密码修改

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass #获取redis的密码
1) "requirepass"
2) ""

127.0.0.1:6379> config set requirepass"123456" #设置redis的密码
оК

#发现所有的命令都没有权限了
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.

127.0.0.1:6379>auth 123456#使用密码进行登录!
оК
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

Redis 持久化

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!

rdb保存的文件是dump.rdb

save 60 5

  • save的规则满足的情况下,会自动触发rdb规则
  • 执行flushall 命令,也会触发我们的rdb规则!
  • 退出redis,也会产生rdb文件!

备份就自动生成一个dump.rdb

文件存放位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"

只需要将rdb文件放在redis启动目录,redis启动的时候会自动检查dump.rdb恢复其中的数据!

  • RDB优点
    • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每六个小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。
    • Redis加载RDB恢复数据远远快于 AOF的方式。
  • RDB缺点
    • RDB方式数据没办法做到实时持久化/ 秒级持久化。因为 bgsave 每次执行都要执行 fork操作创建子进程,属于重量级操作,频繁执行成本过高。
    • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。

AOF

Append Only File ,以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis 启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

AOF保存的是appendonly.aof文件

如果aof 文件有错误,redis启动不起来,可以使用redis-check-aof --fix appendonly.aof 修复aof文件

  • 会将错误所在及以后进行的操作进行抹除
appendonly no	# 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分情况下,rdb完全够用
appendfilename "appendonly.aof" # 持久化的文件名

# appendfsync always	# 每次修改都会同步,消耗性能
appendfsync everysec	# 每秒同步一次,可能会丢失这 1 秒的数据
# appendfsync no		# 不执行同步操作系统自己同步,速度最快

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb	# 文件大于 64 M 会重写

Redis发布订阅

详细讲解

127.0.0.1:6379> PUBLISH kitelee hello,follwer
(integer) 1
127.0.0.1:6379> subscribe kitelee
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kitelee"
3) (integer) 1

1) "message"
2) "kitelee"
3) "hello,follwer"

image-20220510155349285

Redis 主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用主要包括:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接
    主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大
    大提高Redis服务器的并发量。
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:

从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;

从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”。

集群环境配置

# 查看当前库的信息

127.0.0.1:6379> info replication
# Replication
role:master		# 角色
connected_slaves:0	# 没有从机
master_failover_state:no-failover
master_replid:0019086e3bd1710bc3745c90aaa0cb65913e4a81
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

设置 两台从机

打开四个会话

复制三个配置文件

cp redis.conf redis6379.conf 
cp redis.conf redis6380.conf 
cp redis.conf redis6381.conf 

vim redis6379.conf
vim redis6380.conf
vim redis6381.conf

修改配置文件内容

port 6379
pidfile /var/run/redis_6379.pid
logfile "6379.log"
dbfilename dump6379.rdb

##########################################
port 6380
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dbfilename dump6380.rdb

##########################################
port 6381
pidfile /var/run/redis_6381.pid
logfile "6381.log"
dbfilename dump6381.rdb
redis-server usrconf/redis6379.conf 
redis-server usrconf/redis6380.conf 
redis-server usrconf/redis6381.conf 

redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381

设置 一主二从

slaveof

从机输入

# slaveof host port
slaveof 101.201.100.130 6379
# 查看
info replication

image-20220510170224008

如果设置密码主机中查看不到从机, 需要在从机配置中配置主机密码, 长期从机设置也配置在配置文件中

# 主机密码
masterauth <master-password>

# 主机 ip port
replicaof <masterip> <masterport>

image-20220510172616807

主机断开重连,从机依然保持连接,断开期间无法写入

从机断开重连

  • 配置从机依然保持连接

  • 命令行配置的从机则会断开连接,再次命令行设置后,再次连接(全量复制)

Slave启动成功连接到master后会发送一个sync同步命令

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

  • 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

slaveof no one (手动设置)

6379 < < 6380 < < 6381

6379 < < 6380 < < 6381

6380 : slaveof no one

6379 6380 < < 6381

参考资料链接:https://blog.csdn.net/Sophia_0331/article/details/107877248

哨兵模式

Redis Sentinel

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

vim sentinel.conf

sentinel monitor myredis 101.201.100.130 6379 1
sentinel auth-pass myredis 123456

image-20220510211734435

  • 主机断开,哨兵会按要求等待或者直接从从机中选出新的主机,主机再恢复连接也会变为从机
  • 即使在配置文件中配置的主机也会变成从机(哨兵每一次修改主机,都会修改相关的配置文件sentinel.conf,以及redis.conf)
  • 主机从机角色调换过程中,都有可能变为从机,所以主机里也要设置从机登录主机的密码

image-20220510214422066

哨兵模式的配置

# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379			# 如果是哨兵集群,还要为每个哨兵配置端口

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。 
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。

#通知脚本
# shell编程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!

Redis 缓存穿透、缓存击穿、雪崩

原文链接:https://blog.csdn.net/JavaTeachers/article/details/108657809

Redis 缓存穿透

缓存穿透

解决缓存穿透

  • 首先最基本的就是要做参数校验,非法的参数就直接return,连缓存层都到不了。
  • 当请求的数据在穿过Redis后,数据库也返回空,这样的数据也可以存入到缓存中,然后过期时间可以设置一个比较短的时间,这样能够在一定程度上保障后端数据库的安全。
  • 可以使用Redis的布隆过滤器,这个工具可以有效的防止缓存穿透的发生,我们可以将一个参数是否存在保存为一个boolean值,然后需要一个bit就可以存储,这样的数据压缩到一个数据结构中,就是布隆过滤器的原理。即节省存储空间,又能达到效果。

缓存击穿

我们在Redis存储的数据,主要是缓存的效果,目的是为了解决DB的压力,所以一些热点数据,都是先从缓存中获取的,当缓存中不存在的时候再从DB中获取然后再存入缓存。

但是如果一个高频的热点数据,在失效的一瞬间,它的大量请求就会直接打到DB上,这样在DB还没有返回数据给Redis的时候,DB承受了热点请求的压力,就好像缓存是一个水桶,然后突然水桶破了一个洞,直接冲垮了后面的堤坝(DB)。缓存击穿

解决缓存击穿

造成缓存击穿的原因是,在同一时刻从数据库中获取了大量数据,并且设置了相同的过期时间,这些缓存就会在同一时刻失效,这样就造成了缓存击穿的问题。

一些热点的数据,我们可以设置永不过期;或者是在访问数据的时候延长过期时间。

也可以用分布式锁,来锁住数据,保证同一时间只有一个线程能够获取数据,其他请求获取不到数据,只能等待,但是在高并发的场景下,这种方案,体验不太好,并且分布式锁的压力也会特别大。

缓存雪崩

Redis中存储了很多的数据,但是有时候这些数据会出现,在同一个时刻批量过期的情况,因为有可能这些数据是批量插入的,所以他们的过期时间就会都在同一个时间。正好在这个批量数据过期的时间点,大量的请求过来了,因为缓存数据过期了,所以没有命中缓存,直接请求到了数据库中。数据库的压力突然剧增,甚至有可能直接撑不住挂掉。然后有可能DBA会紧急重启DB,但是刚一恢复,新的请求立马又把DB打垮了。

也有可能就是Redis挂了,缓存都不能用了,请求也是直接打到了DB上,然后DB也是扛不住压力,直接挂掉。再恢复,再挂掉。

缓存雪崩

Redis中同一时刻大量的Key过期,那一瞬间和Redis不存在一样,还有Redis真的挂了的情况,这对服务和DB来说是灾难性的问题。

批量存入缓存的数据,我们可以为这些数据分别配置比较合理的过期时间,即使是随机分配过期时间也可以,避免同一时间失效。热点数据永不过期,更新操作时直接更新缓存,但是并不设置过期时间。

当数据库缓存出问题时,可以采用降级措施,虽然是用DB顶上了请求,但是可以通过降级方案,保证某些数据在同一时刻只能有一个线程在查询数据库和写缓存,这样不至于把数据库给搞崩了。

还有就是为了防止Redis挂了,导致的缓存雪崩,可以保证Redis的高可用,就是将Redis集群部署,然后将热点数据都分配到不同的节点上,这样就可以有效的防止雪崩的出现。

其实集中过期,不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在
某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的
宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

  • redis高可用
    • 这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
  • 限流降级
    • 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 数据预热
    • 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值