Redis总结

一、使用场景

一般用作分布式缓存使用, 作为数据库和项目之间的桥梁, redis主要用内存存储数据, 速度非常快, 所以经常大量查询的数据可以放入redis中, 这样可以替关系型数据库抗高并发读取操作.
安装教程:https://blog.csdn.net/qq_37242720/article/details/116696275

二、配置Redis

2.1、redis.conf

# **1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no

# 2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid

# **3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,
#	   因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379

#**4. 绑定的主机地址(默认只允许127.0.0.1Redis发起访问)
bind 127.0.0.1

# 5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300

# 6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose

# 7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout

# **8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
databases 16

# **9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save <seconds> <changes>
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
# 分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

# **10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF(压缩算法)压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

# **11. 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb**

# 12. 指定本地数据库存放目录
dir ./

# 13. 设置当本机为slave服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof <masterip> <masterport>

# 14. 当master服务设置了密码保护时,slave服务连接master的密码
masterauth <master-password>

# **15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
requirepass foobared

# 16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128

# 17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,
# 当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory <bytes>

# 18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
# 因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no

# 19. 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof

# 20. 指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec

# 21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no

# 22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap

# 23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0

# 24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,
# 作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32

# 25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728

# 26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4

# 27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes

# 28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512

# 29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes

# 30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf

2.2、Redis中的内存维护策略

redis作为优秀的中间缓存件,时常会存储大量的数据,即使采取了集群部署来动态扩容,也应该即时的整理内存,维持系统性能。

在redis中有两种解决方案

  • 为数据设置超时时间
  • 采用LRU算法动态将不用的数据删除

2.2.1、为数据设置超时时间

# 设置过期时间
expire key time(以秒为单位)--这是最常用的方式
setex(String key, int seconds, String value)--字符串独有的方式
  • 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
  • 如果没有设置时间,那缓存就是永不过期
  • 如果设置了过期时间,之后又想让缓存永不过期,使用persist key

2.2.2、采用LRU算法动态将不用的数据删除

内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据

  1. volatile-lru:设定超时时间的数据中,删除最不常使用的数据.
  2. allkeys-lru:查询所有的key中最近最不常使用的数据进行删除,这是应用最广泛的策略.
  3. volatile-random:在已经设定了超时的数据中随机删除.
  4. allkeys-random:查询所有的key,之后随机删除.
  5. volatile-ttl:查询全部设定超时时间的数据,之后排序,将马上将要过期的数据进行删除操作.
  6. noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回.
  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

2.3、自定义配置Redis

进入对应的安装目录 /usr/local/redis 修改 redis.conf 配置文件 vim redis.conf (进入命令模式 通过/内容 查找相应字符串)

daemonize no 修改为 daemonize yes 守护进程启动
bind 127.0.01 注释掉 允许除本机外的机器访问Redis服务
requirepass 设置密码 设定数据库密码 (保证服务安全/有些情况下不设定密码是无法进行远程连接访问的)

Redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。但当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(putty,xshell等)都会导致redis进程退出。 服务端开发的大部分应用都是采用后台运行的模式

requirepass设置密码。因为redis速度相当快,所以一台比较好的服务器下,一个外部用户在一秒内可以进行15W次密码尝试,这意味着你需要设定非常强大的密码来防止暴力破解。
可以通过 redis 的配置文件设置密码参数,这样客户端连接到 redis 服务就需要密码验证,这样可以让你的 redis 服务更安全

三、Redis安装与启动

Centos8安装部署Redis6:https://blog.csdn.net/qq_37242720/article/details/116696275

Redis集群:https://blog.csdn.net/qq_37242720/article/details/121010207

四、Redis命令

Redis 命令用于在 redis 服务上执行操作。要在 redis 服务上执行命令需要一个 redis 客户端。

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)等

redis里面的数据都是键值对形式也就是key 和value形式

4.1、常用命令key管理

# 返回满足的所有键 ,可以模糊匹配 比如 keys abc* 代表 abc 开头的 key
keys *

# 查找所有符合给定模式( pattern)的 key 。
# keys 通配符 获取所有与pattern匹配的key,返回所有与该匹配
#	通配符:
#		* 代表所有
#		? 表示代表一个字符
keys pattern

# 是否存在指定的key,存在返回1,不存在返回0
exists key

# 设置某个key的过期时间 时间为秒
expire key second

# 查看你的key是什么类型
type key 

# 删除某个key
del key

# 为给定 key 设置过期时间(以秒计)。
expire key seconds

# 设置 key 的过期时间以毫秒计
pexpire key milliseconds

# 以秒为单位,返回给定 key 还有多少秒过期,-1表示永不过期,-2表示已过期
ttl key

# 以毫秒为单位返回 key 的剩余的过期时间。
pttl key

# 根据value选择非阻塞删除 仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
unlink key

# 修改Key的名称
rename key newkey

# 将当前数据库的 key 移动到给定的数据库 db 当中
move key db

# 移除给定key的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
# 当生存时间移除成功时,返回1。	如果 key 不存在或 key 没有设置生存时间,返回0
persist key

# 选择数据库 数据库为0-15(默认一共16个数据库)设计成多个数据库实际上是为了数据库安全和备份
select index

# 查看当前数据库的key的数量
dbsize

# 将当前数据中的key转移到其他数据库
move key dbindex

# 随机返回一个key
randomkey

# 重命名key
rename key key2

# 打印命令
echo

# 查看数据库的key数量
dbsiz

# 查看数据库信息		
info

# 实时传储收到的请求,返回相关的配置
config get *

# 清空当前数据库
flushdb

# 清空所有数据库
flushall

4.2、应用场景

expire key seconds

  1. 限时的优惠活动信息
  2. 网站数据缓存(对于一些需要定时更新的数据,例如:积分排行榜)
  3. 手机验证码
  4. 限制网站访客访问频率(例如:1分钟最多访问10次)

4.3、Key的命名建议

redis单个key允许存入512M大小

  1. key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
  2. key也不要太短,太短的话,key的可读性会降低;
  3. 在一个项目中,key最好使用统一的命名模式,例如user:123:password;
  4. key名称区分大小写

五、Redis数据类型

5.1、Redis字符串(String)

就是普通的字符串可以作为redis中value的值

# set <key> <value> 添加键值对 
set k1 v100 

# 当k1有值时,值会被替换成v200
set k1 v200  

# setnx <key> <value> 	只有在key不存在时 设置key的值
setnx k1 v300 			# key的值还是v200 因为k1已经存在

# append <key> <value> 	将给定的<value> 追加到原值的末尾
append k1 v400

# strlen <key> 		获得值的长度
strlen k1

# get <key> 		查询对应键值
get k1

# incr <key> 		将key中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
incr k2

# decr <key> 		将key中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
decr k3

# incrby/decrby <key> <步长> 	将 key 中储存的数字值增减。自定义步长。
incrby k4 2
decrby k5 3

# 清空
flushdb

# mset <key1> <value1> <key2> <value2>  .....	 同时设置一个或多个key-value对
mset k1 v1 k2 v2 k3 v3

# mget <key1> <key2> <key3> .....			 	 同时获取一个或多个value  
mget k1 k2 k3

# msetnx <key1> <value1> <key2> <value2>  .....  同时设置一个或多个key-value对,当且仅当所有给定key都不存在。 
# 原子性,有一个失败则都失败
msetnx k3 v3 k4 v4 k5 v5

# getrange <key> <起始位置> <结束位置> 	获得某个范围的值,包含起始位置和结束位置
set user appadmin
getrange user 0 4 # 值为"appad"

# setrange <key> <起始位置> <value>		从<起始位置>开始替换为value和长度个数(索引从0开始)。
setrange user 1 root # 值为"arootmin"

# setex <key> <过期时间> <value> 		设置键值的同时,设置过期时间,单位秒。
setex count 10 600

# getset <key> <value> 	设置新值并获得旧值。
set key v1
getset k1 v2	# 结果为v1
get k1			#结果为v2

数据结构

  • String的数据结构为简单动态字符串,是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
  • 当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

5.2、Reids列表(List)

  • 有序集合,里面的数据可以重复,单键多值
  • 是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
  • 它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
## 赋值语法
# lpush/rpush <key> <value1> <value2> <value3> .... 从左边/右边插入一个或多个值。
lpush k1 100 200 300 100 400

# lpushx/rpushx <key> <value> 将一个值插入到已存在的列表头部/尾部。如果列表不在,操作无效
lpushx k1 500

## 取值语法:
# llen <key>	获得列表长度 
llen k1

# lindex <key> <index>	 按照索引下标获得元素(从左到右)
lindex k1  2

# lrange <key> <start> <stop> 	按照索引下标从左到右获得元素 (0左边第一个,-1右边第一个,0 -1表示获取所有)
lrange k1 0 2
lrange k1 0 -1 # 也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。 

## 删除语法:
# lpop/rpop <key>	从左边/右边吐出一个值。值在键在,值光键亡。
lpop k1

# BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
blpop k1 100

## 修改语法:
# lset <key> <index> <value>		将列表key下标为index的值替换成value
lset k1 3 5

# linsert <key> before <value> <newvalue>		在<value>的后面插入<newvalue>插入值
flsuhdb
lpush k1 100 200 300 400 500 600 500 500 800 500 200
linsert k1 before 2 500 

# lrem <key> <n> <value>			从左边删除n个value(从左到右)
lrem k1 3 500

## 高级语法
# rpoplpush <key1> <key2>		从<key1>列表右边吐出一个值,插到<key2>列表左边。
lpush k2 10 20 30
rpoplpush k1 k2

数据结构

  • List的数据结构为快速链表quickList。
  • 首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
  • 它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
  • 当数据量比较多的时候才会改成quicklist。
  • 因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。
  • Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

5.3、Redis集合(Set)

  • Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
  • Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
  • 一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变
# 清空当前库
flushdb

## 赋值语法
# sadd <key> <value1> <value2> ..... 将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
sadd k1 10 20 30 40

## 取值语法
# scard <key>		返回该集合的元素个数。
scard k1

# smembers <key> 	取出该集合的所有值。
smembers k1

# sismember <key> <value>	判断集合<key>是否为含有该<value>值,有1,没有0
sismember k1 10		# 结果1
sismember k1 50		# 结果0

# srandmember <key> <n> 	随机从该集合中取出n个值 不会从集合中删除 
flushdb		# 清空当前库
sadd k1 10 20 30 40 50 60 70 
srandmember  k1 3

# srem <key> <value1> <value2>	 .... 删除集合中的某个元素。
srem k1 10 20
smembers k1			# 结果30 40

# spop <key>		随机从该集合中吐出一个值
flushdb		# 清空当前库
sadd k1 10 20 30 40
spop k1

## 删除语法:
# srem <key> member1 [member2] :移除集合中一个或多个成员
srem k1 10 20

# spop <key>[count] :移除并返回集合中的一个随机元素
spop k1

# smove <key1> <key2> value	把集合key1中value移动到另一个集合key2中
flushdb		# 清空当前库
sadd k1 10 20 30 40 50 60 70 80
smove k1 k2 10

## 交集语法
# sinter <key1> <key2>		返回两个集合的交集元素
flushdb		# 清空当前库
sadd k1 10 20 30
sadd k2 20 30 40
sinter k1 k2				# 结果为 20 30

## 并集语法
# sunion <key1> <key2>		返回两个集合的并集元素
sunion k1 k2				# 结果为 10 20 30 40

## 差集语法
# sdiff <key1> <key2>		返回两个集合的差集元素(key1中的,不包含key2中的)
sdiff k1 k2					# 结果为 10

数据结构

  • Set数据结构是dict字典,字典是用哈希表实现的。
  • Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

5.4、Redis哈希(Hash)

Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储

# 清空当前库
flushdb

# hset <key> <field> <value>给<key>集合中的  <field>键赋值<value>  新版中hset和hmset没有什么区别
hset user:0 id 0
hset user:1 id 1 name zhangsan age 18

# hmset <key1> <field1> <value1> <field2> <value2>... 批量设置hash的值
hmset user:2 id 2 name lisi age 18 sex 男

# hget <key1> <field>		从<key1>集合<field>取出 value 
hget user:1 name

# hmget key field[field1] :获取key所有给定字段的值
hmget user:1 id name age

# hexists <key1> <field>	查看哈希表 key 中,给定域 field 是否存在。 
hexists user:1 age

# hkeys <key>				列出该hash集合的所有field
hkeys user:1

# hvals <key>				列出该hash集合的所有value
hvals user:1

# hgetall <key>返回HASH表中所有的字段和值
hgetall user:1

# hlen <key> 获取哈希表中字段的数量
hlen user:1

# hincrby <key> <field> <increment>	为哈希表 key 中的域 field 的值加上增量 1   -1
hincrby user:1 age 1
hincrby user:1 age -3

# hsetnx <key> <field> <value>	将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .
hsetnx user:3 id 3
hsetnx user:3 age 20
hsetnx user:3 age 18	# 不生效,因为user:3中age已存在

# hdel <key> field1[field2 删除一个或多个HASH表字段
hdel user:3 id age

数据结构

  • Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。
  • 当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

应用场景
Hash的应用场景:(存储一个用户信息对象数据)

  1. 常用于存储一个对象 / 分布式Session
  2. 为什么不用string存储一个对象?

hash是最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成hashmap存放在redis中。

用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,

主要有以下2种存储方式:

  • 第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。
  • 第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

总结: Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。

5.5、有序集合ZSet类型(sorted set)

  • Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
  • 不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了。
  • 因为元素是有序的,所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
  • 访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

常用命令

# 清空当前库
flushdb

## 赋值语法
# zadd <key> <score1> <value1> <score2> <value2>… 	将一个或多个member元素及其score值加入到有序集key当中。
zadd k1 100 zs 90 ls 80 ww 75 zl 70 sq 66 zb

## zcard <key>:获取有序集合的成员数
zcard k1

# zcount <key><min><max>统计该集合,分数区间内的元素个数 
zcount k1 70 100

# zrank <key><value>返回该值在集合中的排名,从0开始。
zrank k1 zs

# zrange <key> <start> <stop>  [WITHSCORES]  返回有序集 key 中,下标在<start><stop>之间的元素,带WITHSCORES,可以让分数一起和值返回到结果集。
zrange k1 0 -1
zrange k1 0 -1 withscores

zrange k1 0 3
zrange k1 0 3 withscores

# zrangebyscore key min max [withscores] [limit offset count] 	返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。 
zrangebyscore k1 77 96
zrangebyscore k1 77 96 withscores
 
# zincrby <key> <increment> <value>      为元素的score加上增量
zincrby k1 -2 zs	# 结果100-2=98
zincrby k1 3 ls  	# 结果90+3=93
 
# zrem <key> <value>	删除该集合下,指定值的元素 
zrem k1 ls

数据结构

  • SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
  • zset底层使用了两个数据结构
    • (1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
    • (2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

5.6、特殊数据类型-Bitmaps(位存储)

  • 统计信息,活跃、不活跃、登录、未登录打卡,Bitmaps位图,数据结构,都是操作二进制来进行记录,就只有0和1两个状态
flushdb
# setbit <key> <offset> <value>设置Bitmaps中某个偏移量的值(0或1)
setbit user:202105 1 1
setbit user:202105 2 1
setbit user:202105 5 1
# getbit <key> <offset>获取Bitmaps中某个偏移量的值
> getbit user:202105 1
(integer) 1
127.0.0.1:6379> getbit user:202105 2
(integer) 1
127.0.0.1:6379> getbit user:202105 3
(integer) 0
127.0.0.1:6379> getbit user:202105 5
(integer) 1

5.7、特殊数据类型-Hyperloglog(基数统计的算法)

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。

但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:

  • 数据存储在MySQL表中,使用distinct count计算不重复个数
  • 使用Redis提供的hash、set、bitmaps等数据结构来处理

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

能否能够降低一定的精度来平衡存储空间?

  • Redis推出了HyperLogLog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

  • 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
# pfadd <key>< element> [element ...]   添加指定元素到 HyperLogLog 中
127.0.0.1:6379> pfadd program php
(integer) 1
127.0.0.1:6379> pfadd program java
(integer) 1
127.0.0.1:6379> pfadd program python
(integer) 1

# pfcount <key> [key ...] 计算HLL的近似基数,可以计算多个HLL,比如用HLL存储每天的UV,计算一周的UV可以使用7天的UV合并计算即可
127.0.0.1:6379> pfcount program
(integer) 3

5.8、特殊数据类型-Geospatial(地理位置 找附近的人)

Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

# geoadd <key>< longitude><latitude><member> [longitude latitude member...]   添加地理位置(经度,纬度,名称)
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing

# geopos  <key> <member> [member...]  获得指定地区的坐标值
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.47000163793563843"
   2) "31.22999903975783553"
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.38000041246414185"
   2) "39.90000009167092543"

# geodist <key> <member1> <member2>  [m|km|ft|mi ]  获取两个位置之间的直线距离
# 单位:
# m 表示单位为米[默认值]。
# km 表示单位为千米。
# mi 表示单位为英里。
# ft 表示单位为英尺。
# 如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位
127.0.0.1:6379> geodist china:city beijing shanghai km
"1068.1535"
127.0.0.1:6379> geodist china:city beijing chongqing km
"1462.9505"
127.0.0.1:6379> geodist china:city beijing chongqing m
"1462950.5002"

# georadius<key>< longitude><latitude>radius  m|km|ft|mi   以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
2) "shenzhen"

六、redis持久化方案

redis中持久化方案一共有两种,

  • 一种叫做RDB模式,
  • 一种叫做AOF模式

6.1、RDB模式(默认)

使用场景:

RDB模式是redis中默认的持久化模式, 叫做分时持久化, 可以在配置文件中配置持久化时间, 和持久化时间的频率. 一般如果redis做分布式缓存使用, 不怕丢数据, 可以用这种持久化方案

触发机制

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

恢复rdb文件

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

优点

  • 适合大规模的数据恢复
  • 对数据的完整性要求不高

缺点

  • 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次的数据就没有了
  • Fork(子进程)进行的时候,会占用一定的内存空间

6.2、AOF模式

使用场景:

将我们所有的命令都记录下来,恢复的时候把这个文件全部执行一遍,保存的文件是appendonly.aof,默认是不开启的,需要手动配置,然后重启redis,
如果aof有错位,这个时候reidis是启动不起来的,我们需要修复aof文件

优点

  • 每一次修改都同步,文件的完整性会更好
  • 每秒同步一次,可能会丢失一秒数据
  • 从不同步,效率是最高的

缺点

  • 相对于数据文件来说,aof大于rdb,修复也比rdb慢
  • aof运行效率比rdb慢,所以redis默认是rdb
  • aof默认就是文件无限追加,所以文件越来越大

七、缓存穿透怎么办

7.1、问题描述

正常访问流程
如果我们高并发查询, 首先到redis中查询, 如果redis中有数据直接返回结果.

如果redis中没有数据, 则到mysql数据库中查询, 如果查询到了, 将查询到的数据存入redis中一份, 然后再将数据返回给客户

下载再查询这样的数据, 由于redis中已经存在, 则直接返回数据, 不会去查询数据库, 降低了数据库的高并发访问压力

异常流程

客户高并发查询, 首先访问redis, redis中没有数据, 查询mysql数据库, mysql数据库中也查询不到数据

如果是同样的这种方式查询, 也就是相当于查询操作, 通过了redis, 并且访问了mysql数据库, mysql数据库无法抗住这样的高并发压力, 会造成mysql服务器宕机, 这就是著名的缓存穿透问题.

7.2、问题解决方案

缓存预热流程解决方案

将数据存入redis, 和读取流程分开

也就是管理员将数据从mysql中查询到, 存入redis中

消费者访问查询的时候, 只查询redis, 如果redis中没有数据则返回null

布隆过滤器

布隆过滤器其实就是利用了一致性hash算法, 使用算法的特点, 将数据均匀分布, 还有利用了概率学的原理, 用算法判断数据是否存在, 如果存在就去查询, 如果不存在就直接返回null.避免了数据不存在还去访问mysql数据库.

八、redis集群

8.1、redis单机版

使用场景:
传统项目企业公司, 一般做oa, erp, 电子政务系统, 比如政府项目等
数据量和并发量不高的情况下使用

优点:
简单, 省服务器

缺点:
无法抗高并发, 大数据量, 没有灾备方案

8.2、读写分离方案

描述:
一台主机, 多台备机, 主机只允许写入, 不允许人为读取, 备机只允许读取操作, 不允许人为写入操作, 写入只能往主机中写入数据, 主机会将数据自动同步到备机中

使用场景:
中小型规格的互联网公司, 并发量不是特别大的情况下使用, 主要是读一定要远远大于写入操作量使用

优点:
部署相对简单, 大大提高并发读取操作量

缺点:
灾备方案不完整, 一旦主机挂掉, 整个集群将只能读取, 不允许写入.

8.3、哨兵模式

描述:

  1. 一台主机, 多台备机, 还有一个sentinel哨兵服务器
  2. 哨兵服务器连接所有redis主机和备机服务器, 并且对主机进行ping, pong操作, 进行监听
  3. 主机只允许写入, 备机只允许读取, 我们向主机中写入数据, 数据由主机自动同步到备机中
  4. 如果哨兵服务器向主机发送ping命令, 主机没有给哨兵服务器发送pong命令, 一段时间后认为最近死掉, 哨兵服务器会从备机中选出一个作为新的主机, 这种叫做主观下线
  5. 如果哨兵服务器向主机发送ping命令, 主机没有给哨兵服务器发送pong命令, 一段时间后, 主机会问每一个备机, 让备机去ping主机,看主机是否返回pong命令, 如果主机不给备机返回pong命令则认为主机真的死掉, 这时候再选举出一个备机作为新的主机, 这种叫做客观下线.

使用场景:
中小型规模互联网公司使用, 灾备, 读写分离功能都具有, 主要用在读取远远大于写入操作的场景中

优点

  • 哨兵集群,基于主从复制模式,所有主从配置优点,它全有
  • 主从可以切换,故障可以转移,系统的可用性就会更好
  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点

  • redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
  • 实现哨兵模式的配置,其实是很麻烦的,里面有很多选择
  • 不适合用在写入并发高的场景中. 由于客观下线使用了流言传播协议(Gossip), 这个协议会导致有延时.主备机之间切换有一定的延时.

8.4、Redis内置集群

描述:

  1. redis内置集群至少需要6台服务器, 三台主机, 三台备机
  2. 主机和备机之间有心跳检测机制, 备机向主机发送ping命令, 主机返回备机pong命令主机宕机, 备机替代主机工作
  3. 我们读写都只操作主机, 主机会将数据自动同步到备机, 这样主机死掉, 备机中具有主机的全部数据, 防止数据丢失
  4. redis内置集群, 一共最多有0-16383个slot槽位, 我们需要将这些个槽位分配给不同的主机
  5. 我们不管读取还是写入数据, redis集群都会根据key使用CRC16算法, 将key算出来一个纯数字,
  6. 这个crc16算法算出来的数字对16384取膜, 也就是数字对16384做除法取余数, 余数一定是在0-16383范围内. 看这个余数在哪个slot槽范围内, 就去对应的服务器, 读或者写.

使用场景:
大型互联网公司, 对读和写入操作都比较多, 并发高, 数据量大的情况下使用

优点:

  • 灾备(数据不容易丢失, 主机和备机中数据一样),
  • 高可用(主机死了, 备机替代主机工作),
  • 扩展性好(当前集群服务器数量不够, 可以再加)
  • 负载均衡(通过slot槽和crc16算法进行计算, 决定数据存入哪台服务器, 具有天然的负载均衡特性, 可以抗高并发)

缺点:
由于slot槽总数是0-16383所以集群内主机最多允许有16384个服务器, 再多不支持.

8.5、TwemProxy集群方案

使用场景:

这种方案是Twitter公司的redis集群方案, 也是Twitter推出并且开源免费的技术
国内知乎使用的也是这个技术方案

描述:

	1. 在这种方案中, 一共有2^32次方个桶的空间,也就是有0~(2^32)-1 = 4,294,967,296的数字空间.
	2. 将这些数字空间均匀分配到hash环中
	3. redis服务器的ip地址使用hash算法进行计算会得出一个数字, 这个数字看在桶空间的那个位置, 那么这台服务器就处于这个桶空间的位置.
	4. 如果我们存取数据, twemproxy代理服务器会使用hash算法对key进行计算, 算出的这个数字, 看在hash环中的哪个位置, 按照顺时针方向, 找到最近的一台redis服务器进行存取.

优点:

使用的服务器至少3台, twemproxy一台, 两台redis服务器. 使用服务器数量较少
由代理服务器控制真实的redis服务器, 所以部署简单
具有负载均衡功能, 可以抗高并发大数据量的读写
扩展比较容易, 只需要在twemproxy代理服务器的配置文件中更改配置即可

缺点:

高可用不太好, 容易出现一台redis服务器宕机, 那么里面的数据将会丢失.

九、Redis6新特性

9.1、ACL

Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。
在Redis 5版本之前,Redis 安全规则只有密码控制 还有通过rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制 :

  • 接入权限:用户名和密码
  • 可以执行的命令
  • 可以操作的 KEY

参考官网: https://redis.io/topics/acl.

# acl list 展现用户权限列表
acl list

# acl cat 查看添加权限指令类别 
acl cat
# 加参数类型名可以查看类型下具体命令
acl cat list

# acl whoami 查看当前用户
acl whoami

# clsetuser 创建和编辑用户ACL

在这里插入图片描述

# 通过命令创建新用户默认权限 没有指定任何规则。
# 如果用户不存在,这将使用just created的默认属性来创建用户。
# 如果用户已经存在,则上面的命令将不执行任何操作。
acl setuser user1

# 设置有用户名、密码、ACL权限、并启用的用户
acl setuser user2 on >password ~cached:* +get

# 切换用户,验证权限
127.0.0.1:6379> acl whoami
"default"
127.0.0.1:6379> auth user2 password
OK
127.0.0.1:6379> acl whoami
(error) NOPERM this user has no permissions to run the 'acl' command or its subcommand
127.0.0.1:6379> get foo
(error) NOPERM this user has no permissions to access one of the keys used as arguments
127.0.0.1:6379> get cached:1121
(nil)
127.0.0.1:6379> set cached:1121 1121
(error) NOPERM this user has no permissions to run the 'set' command or its subcommand

9.2、多线程

Redis6终于支撑多线程了,告别单线程了吗?

IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程。

原理架构

Redis 6 加入多线程,但跟 Memcached 这种从 IO处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。整体的设计大体如下
在这里插入图片描述
多线程IO默认也是不开启的,需要再配置文件中配置

io-threads-do-reads  yes 
io-threads 4

9.3、工具支持 Cluster

之前老版Redis想要搭集群需要单独安装ruby环境,Redis 5 将 redis-trib.rb 的功能集成到 redis-cli 。另外官方 redis-benchmark 工具开始支持 cluster 模式了,通过多线程的方式对多个分片进行压测。
在这里插入图片描述

9.4、Redis6其它新功能

  • RESP3新的 Redis 通信协议:优化服务端与客户端之间通信
  • Client side caching客户端缓存:基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据cache到客户端。减少TCP网络交互。
  • Proxy集群代理模式:Proxy 功能,让 Cluster 拥有像单实例一样的接入方式,降低大家使用cluster的门槛。不过需要注意的是代理不改变 Cluster 的功能限制,不支持的命令还是不会支持,比如跨 slot 的多Key操作。
  • Modules API: Redis 6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。

十、Redis的发布和订阅

10.1、什么是发布和订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。

10.2、Redis的发布和订阅

1、客户端可以订阅频道如下图
在这里插入图片描述
2、当给这个频道发布消息后,消息就会发送给订阅的客户端
在这里插入图片描述

10.3、发布订阅命令行实现

1、 打开一个客户端订阅channel1

subscribe java521

在这里插入图片描述
2、打开另一个客户端,给channel1发布消息hello

publish java521 hello world

在这里插入图片描述
返回的2是订阅者数量
3、打开第一个客户端可以看到发送的消息
在这里插入图片描述
注:发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息

十一、Spring Cache

自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象,在此之前一般通过AOP实现。

使用Spring Cache的好处:

  • 提供基本的Cache抽象,方便切换各种底层Cache;
  • 通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
  • 提供事务回滚时也自动回滚缓存;
  • 支持比较复杂的缓存逻辑;

11.1、常见注解

Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素

@Cacheable

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,(需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的)

  • @Cacheable:触发缓存写入。
  • @CacheEvict:触发缓存清除。
  • @CachePut:更新缓存(不会影响到方法的运行)。
  • @Caching:重新组合要应用于方法的多个缓存操作。
  • @CacheConfig:设置类级别上共享的一些常见缓存设置。

11.2、基本概念

名称解释
Cache缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager缓存管理器,管理各种缓存(cache)组件
@Cacheable主要针对方法配置,能够根据方法的请求参数对其进行缓存
@CacheEvict驱逐缓存
@CachePut保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略
@CacheConfig统一配置本类的缓存注解的属性

@Cacheable/@CachePut/@CacheEvict 主要的参数

名称解释
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
unless否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”)
allEntries(@CacheEvict )是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation(@CacheEvict)是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)

11.3、SpEL上下文数据

SpEL (Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称位置描述示例
methodNameroot对象当前被调用的方法名#root.methodname
methodroot对象当前被调用的方法#root.method.name
targetroot对象当前被调用的目标对象实例#root.target
targetClassroot对象当前被调用的目标对象的类#root.targetClass
argsroot对象当前被调用的方法的参数列表#root.args[0]
cachesroot对象当前方法调用使用的缓存列表#root.caches[0].name
Argument Name执行上下文当前被调用的方法的参数如findArtisan(Artisan artisan),可以通过#artsian.id获得参数#artsian.id
result执行上下文方法执行后的返回值(当方法执行后的判断有效, 如 unless cacheEvict的beforeInvocation=false)#result

SpEL提供了多种运算符

类型运算符
关系<,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术+,- ,* ,/,%,^
逻辑&&,
条件?: (ternary),?: (elvis)
正则表达式matches
其他类型?.,?[…],![…],1,$[…]

十二、总结

单机版方案:

适合读取和写入并发量不高的情况下使用, 一些做传统项目的公司使用

读写分离方案:

可以抗高并发读取, 无法抗高并发写入, 小公司使用

哨兵模式方案:

高可用, 可以抗高并发读取, 无法抗高并发写入. 小公司使用

redis内置集群:

高可用, 负载均衡, 可扩展, 灾备. 适合大型互联网公司使用, 可以抗高并发读写

不怕数据丢失, 但是使用的服务器数量比较多, 拿redis作为主要存储服务器使用.

twemproxy集群方案(主流集群方案):

负载均衡, 可扩展, 适合大型互联网公司使用, 可以抗高并发读写

怕数据丢失, 所以如果使用redis作为分布式缓存使用, 使用这种方案


  1. ↩︎

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值