Redis

Redis

redis中文文档https://www.redis.net.cn/tutorial/3501.html

面试题:https://www.cnblogs.com/jasontec/p/9699242.html

Redis基本

Redis安装

1、安装gcc运行环境,由于redis是由c语言写的,所以这里必须先安装gcc环境才可以安装redis。

yum -y install gcc automake autoconf libtool make

2、下载redis

可以在官网下载redis的压缩包。也可以通过linux进行在线安装。

$ wget http://download.redis.io/releases/redis-5.07.tar.gz//下载压缩包
$ tar zxvf redis-5.0.8.tar.gz -C /opt  //解压安装包到opt的路径下
$ cd /opt/redis-5.0.8 && make MALLOC=libc//解压并且编译

3、安装redis

make PREFIX=/usr/local/redis install//安装到指定目录下

4、进入到/usr/local/下,查看有没有redis,如果有继续进入,在bin目录下可以看到以下结构:

查看有无redis安装

运行服务端和客户端

./redis-server

运行服务端。

运行服务端的redis

可以看到端口号是:6379。

退出输入:Ctrl+c。

运行客户端标准写法。

redis-cli -h IP地址 -p 端口

也可简便直接进行运行,相当于端口号ip地址都默认了。

./bin/redis-cli
运行客户端,基础测试

这里注意客户端和服务端同时打开!我们可以在客户端进行存值取值,就代表服务端启动成功了。

查看当前redis运行的端口号:

ps -ef | grep -i redis
查看运行redis的端口号

Redis配置

Redis默认定义了很多配置,但是在实际开发中,一般都是我们通过手动配置完成。

回到解压文件下,我们可以看到redis.conf,这个就是配置文件。我们需要把它复制到安装目录下。

cp /opt/redis-5.0.8/redis.conf /usr/local/redis
Redis配置文件

进入这个配置文件

用vim编辑器打开配置文件

这里我们可以看到bind 127.0.0.1这个代表只允许本机访问

port端口号6379.

Redis配置文件文档4.0

前10个:

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. 绑定的主机地址
  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 命令在连接上指定数据库id
  databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
  save  
  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

我们如果需要修改,可以使用linux正常模式下的查找:

/daemonize

注意,带#是注释!不要混淆了。

中间10个:

11. 指定本地数据库文件名,默认值为dump.rdb
  dbfilename dump.rdb
12. 指定本地数据库存放目录
  dir ./
13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
  slaveof  
14. 当master服务设置了密码保护时,slav服务连接master的密码
  masterauth 
15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
  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 
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

Redis内存维护策略

由于Redis作为缓存,本质就是在内存中读写,会非常占用内存,我们应该及时的整理内存,维持系统性能。

解决方法一:为数据设置超时时间

相关应用:短信验证码。

命令查看数据时间

这里我们可以看到,我们设置的两个key的有效值,都是永久有效。这里的-1代表永久有效。

expire name 1500

这里相当于设置这个key在内存中的时间,设置name1500有效。

设置key在内存中的保存时间

如果过了1500s,就会出先-2,代表已经不存在内存中。

-2代表该数据已不存在内存中

解决方法二:采用LRU算法动态将不用的数据删除

LRU算法删除不用的数据

这个LRU算法在配置文件中设置,默认全部注释了。

1.volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放

2.allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放,

前两种是经场用的。

3.volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放

4.allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放

5.volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作

6.noeviction:不删除任意数据(但redis还会根据引用计数器进行释放呦~),这时如果内存不够时,会直接返回错误
7、
8、(7,8是4版本以上才有的)

自定义配置Redis

将redis改为守护进程启动

首先打开redis.conf文件,搜索daemonize进行更改。

改成守护进程.png

将bind 127.0.0.1注释掉,允许本机以外的机器访问redis

在配置文件中设置redis外网也可以访问

需要为我们的redis设置密码

为什么?

因为redis速度相当快,在一台比较好的服务器,一个外部用户可以在一秒以内进行15w次的密码尝试,这意味着你需要设定更加强大的密码来防止暴力解锁。

requirepass 设置密码 设置数据库密码 (有些情况下不设定密码是无法进行远程访问的)
配置密码
进入客户端输入配置文件中设置的密码

注意**:如果自定义配置了redis.conf,在启动服务端的时候,也要写上配置文件。

./bin/redis-server ./redis.conf

加载配置文件后,我们进行启动。

查看是否已守护进程启动

可以看到,redis的服务端已经按配置文件中的守护进程打开。

关闭Redis

非正常模式关闭:关闭后台进程,断电。

如果以非正常模式进行关闭,redis客户端存入的数据也没有默认情况下保存(900s更新一次),那么数据就会丢失。

shutdown

在客户端输入shutdown,是正常模式关闭,可以保存redis的数据。

远程连接

Redis可视化工具:Redis Desktop Manager.

步骤:

1、下载Redis Desktop Manager工具

2、打开阿里云服务器安全组的redis端口号

3、在redis的配置文件中修改非本机访问(bind),添加密码

4、在linux上启动redis的服务端

5、在windwos上启动工具,进行连接。

使用Docker进行安装Redis

//目前对docker使用还不太好

1、探索redis

docker search redis

2、下载镜像

docker pull redis

3、创建并且运行


常用的指令

  • keys * 返回满足的所有键,还可以模糊匹配

  • del key 删除一个key

  • exists key:检查key是否存在,存在返回1,不存在返回0

  • expire key second:为key设定过期时间

EXPIRE key second的使用场景:
1、限时的优惠活动
2、网站数据缓存
3、手机验证码
4、限制网站访客频率


* ttl key  返回key剩余时间,当key不存在的时候,返回-2.

* PERSIST key:移除key的过期时间,key将持久保存

* select: 选择数据库,redis默认16个数据库

* move key db:移动key至指定数据库中

* random key:随机返回一个key

* rename key newkey:修改key的名称

* each:打印命令

* dump key:序列化给定key,返回被序列化的值

* type key:返回key所储存的值的类型

* key pattern:查询所有符号给定模式的key

* flushdb:清空当前数据库

* flushall:清空所有数据库

* info:查看数据库信息

### key的命名建议

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

由于redis是非关系数据库,所以最好遵循以上的命名建议。

## Redis中的数据类型

### string类型(字符串)

* string类型是Reds最基本的数据类型,一个键最大能存储512MB
* string数据结构是简单的 key-value类型,vaue其不仅是 string,也可以是数字,是包含很多种类型的特殊类型string类型是二进制安全的。意思是reds的 string可以包含任何数据
* 比如序列化的对象进行存储,比如一张图片进行二进制存储,比如一个简单的字符串数值等等。

基本使用方法

```bash
127.0.0.1:6379> set k1 v1 # 创建
OK
127.0.0.1:6379> get k1 # 获取
"v1"
127.0.0.1:6379> keys * # 获取所有
1) "k1"
127.0.0.1:6379> EXISTS l #l 是否存在
(integer) 0
127.0.0.1:6379> EXISTS k1
(integer) 1
127.0.0.1:6379> STRLEN k1 # 返回k1的长度
(integer) 2
127.0.0.1:6379> APPEND k1 hahah # 向k1后添加字符串长度
(integer) 7
127.0.0.1:6379> get k1
"v1hahah"  # 这里我们发现k1后已经添加了hahah
127.0.0.1:6379> APPEND k2 hahah # 如果我们追加的字符串并不存在,则自动创建
(integer) 5
127.0.0.1:6379> get k2
"hahah"

127.0.0.1:6379> set views 0 
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCR views # 加一
(integer) 1
127.0.0.1:6379> INCR views
(integer) 2
127.0.0.1:6379> DECR views #减一
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> INCRby views 10 #加10
(integer) 11
127.0.0.1:6379> DECRBY views 10#减10
(integer) 1

获得指定长度:

127.0.0.1:6379> set key1 1234567
OK
127.0.0.1:6379> get key1
"1234567"
127.0.0.1:6379> GETRANGE key1 0 -1 # 获取所有
"1234567"
127.0.0.1:6379> GETRANGE key1 0 4 # 右边为闭区间
"12345"

从指定位置开始替换:

127.0.0.1:6379> set k abcdef
OK
127.0.0.1:6379> get k
"abcdef"
127.0.0.1:6379> SETRANGE k 1 123 #从下标1开始替换123
(integer) 6
127.0.0.1:6379> get k
"a123ef"

设置过期时间:setex:

127.0.0.1:6379> setex k2 30 "neirong" #设置k2的过期时间为30s
OK
127.0.0.1:6379> ttl k2 # 可以查看当前k2键的有效时间
(integer) 26
127.0.0.1:6379> ttl k2
(integer) 24
127.0.0.1:6379> get k2
"neirong"
127.0.0.1:6379> ttl k2
(integer) 9
127.0.0.1:6379> ttl k2 # 这里我们发现已经过期了
(integer) -2

不存在才使用:setnx:(在分布式锁中经常使用)

127.0.0.1:6379> setnx k2 hhhhh #这里k2不存在,可以进行设置
(integer) 1
127.0.0.1:6379> get k2
"hhhhh"
127.0.0.1:6379> keys *
1) "k2"
2) "k"
127.0.0.1:6379> setnx k2 qqqq # k2已经存在,不可以进行设置
(integer) 0
127.0.0.1:6379> get k2
"hhhhh"

一次创建多个key:

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset k1 1 k2 2 k3 v3
OK
127.0.0.1:6379> mget k1 k2 k3
1) "1"
2) "2"
3) "v3"
注意这里也可以用msetnx创建多个不存在的k(这是原子性操作,一个失败都失败。)

创建一个对象:

127.0.0.1:6379> mset user:1:name zhangsan user:2:age 18
OK
127.0.0.1:6379> mget user:1:name user:2:age
1) "zhangsan"
2) "18"

应用场景

  • String类型通常用于保存单个字符串或Json字符串数据
  • 因String是二进制安全,所以你可以把一个图片的内容作为字符串来存储。
  • 计数器(常用的key-value缓存应用,常规计数:微博数,粉丝数)

Hash类型(哈希)

key(map)--> key (k - V)

存取一个:

hset myhash filed value
hmset myhash filed value filed2 value2

127.0.0.1:6379> hset myhash field SZW  #存入一个hash类型
(integer) 1
127.0.0.1:6379> hget myhash field   # 取出一个hash类型
"SZW"

存取多个:

hmset myhash filed value
hmget myhash filed

127.0.0.1:6379> hmset myhash name szw age 18 address aynu
OK
127.0.0.1:6379> hmget myhash name age address
1) "szw"
2) "18"
3) "aynu"

取出所有的:

127.0.0.1:6379> hgetall myhash
1) "name"
2) "szw"
3) "age"
4) "18"
5) "address"
6) "aynu"

删除hash中的字段,对应的value也会消失:

127.0.0.1:6379> hdel myhash name age
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "address"
2) "aynu"

获取hash有几个字段:

127.0.0.1:6379> hgetall myhash
1) "address"
2) "aynu"
3) "name"
4) "szw"
5) "age"
6) "20"
127.0.0.1:6379> hlen myhash
(integer) 3

判断hash中的指定字段是否存在:

127.0.0.1:6379> hexists myhash name
(integer) 1
127.0.0.1:6379> hexists myhash nohave
(integer) 0

获取所有的key和values

127.0.0.1:6379> hkeys myhash
1) "address"
2) "name"
3) "age"
127.0.0.1:6379> hvals myhash
1) "aynu"
2) "szw"
3) "20"

对数字类型的字段可以自增:

127.0.0.1:6379> hincrby myhash id 1
(integer) 2
127.0.0.1:6379> hsetnx myhash testHave 1 #如果不存在这个字段,可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash testHave 1#存在这个字段,就不可以设置
(integer) 0

List类型(列表)

在redis中,我们可以把list玩成栈、队列、阻塞队列。

所有的llist命令都是以l开头的。

127.0.0.1:6379> lpush list one  # push是存
(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> lrange list 0 -1    #取出所有的是0 -1
1) "three"
2) "two"
3) "one"

我们可以看到,我们这个list当成一个栈,我们相当于从头部添加了数据。

127.0.0.1:6379> rpush list four # 用rpush对list的右边进行存执
(integer) 4
127.0.0.1:6379> lrange list 0 -1 # 用了rpush后,我们可以重新看这个list,four是在最底部
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> lrange list 0 -1
1) "two"
2) "one"

查看下标为1的问题

linde list n

127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1   # 查看下标为1的值
"one"

判断列表的长度

Llen

127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> Llen list
(integer) 2

移除指定的值,精确匹配

Lrem

127.0.0.1:6379> lrange list  0 -1
1) "one"
2) "one"
3) "four"
4) "two"
5) "one"
6) "five"
127.0.0.1:6379> lrem list 2 one # 移除两个值为one的数据
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "two"
3) "one"
4) "five"

截取

127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "four"
3) "two"
4) "one"
5) "five"
127.0.0.1:6379> ltrim list 1 3 # 这里截取list中下标为1开始的三个数据
OK
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "two"
3) "one"

移除最后一个元素,并且将这个元素移动到另外一盒key上

127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "two"
3) "one"
127.0.0.1:6379> rpoplpush list list2    # 在list的右边pop一个数据,放入list2中
"one"
127.0.0.1:6379> keys *  # 这里我们发现多了一个list2
1) "myhash"
2) "list2"
3) "list"
127.0.0.1:6379> lrange list2 0 -1
1) "one"

更改list中某个下标的值

127.0.0.1:6379> exists list3    # 判断该list 是否存在
(integer) 0
127.0.0.1:6379> lset list3 0 hah    #我们尝试修改下标为0的数据,发现报错。
(error) ERR no such key
127.0.0.1:6379> lpush list3 qqq # 创建并且在list中传入qqq
(integer) 1
127.0.0.1:6379> lrange list3 0 0    # 查询可知有数据qqq
1) "qqq"
127.0.0.1:6379> lset list3 0 hah    # 将hah修改坐标为0的数据
OK
127.0.0.1:6379> lrange list3 0 0    # 查询发现修改成功
1) "hah"

在list中向后或者向前插入数据

127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "two"
127.0.0.1:6379> LInsert list before two "insertValue" # 在list中two数据前插入insertvalue数据
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "insertValue"
3) "two"

Set(集合)

set中的值是不能重复的。无序不重复

创建set,向set中添加值,查看set。

127.0.0.1:6379> sadd myset szw # 添加myset并且添加元素szw 
(integer) 1
127.0.0.1:6379> sadd myset ss
(integer) 1
127.0.0.1:6379> sadd myset ww
(integer) 1
127.0.0.1:6379> smembers myset # 查看myset中的元素
1) "ss"
2) "ww"
3) "szw"

判断set中是否有某个值

sismember key 值

127.0.0.1:6379> smembers myset
1) "ss"
2) "ww"
3) "szw"
127.0.0.1:6379> sismember myset szw
(integer) 1

判断set中的有几个值

127.0.0.1:6379> scard myset
(integer) 3

移除set中的某个值

127.0.0.1:6379> smembers myset #查看未移除前的set
1) "ss"
2) "ww"
3) "szw"
127.0.0.1:6379> srem myset ss # 移除ss
(integer) 1
127.0.0.1:6379> smembers myset # 查看移除ss后的set
1) "ww"
2) "szw"

随机抽选出指定个数的元素

127.0.0.1:6379> srandmember myset 2 
1) "ww"
2) "szw"
127.0.0.1:6379> srandmember myset
"ww"
127.0.0.1:6379> srandmember myset
"ww"
127.0.0.1:6379> srandmember myset
"szw"

移除指定的key,随机删除key。

127.0.0.1:6379> SMEMBERS myset
1) "qq"
2) "ww"
3) "szw"
4) "aa"
5) "bb"
6) "cc"
127.0.0.1:6379> spop myset  # 随机移除myset中的一个元素
"aa"
127.0.0.1:6379> spop myset
"ww"
127.0.0.1:6379> spop myset 2 # 随机移除myset中的两个元素
1) "bb"
2) "szw"
127.0.0.1:6379> SMEMBERS myset #这里我们发现被移除以上元素后,只剩下了两个
1) "cc"
2) "qq"

将一个指定的key移除到另外一个key中

127.0.0.1:6379> SMEMBERS myset
1) "ww"
2) "qq"
3) "rr"
4) "ee"
127.0.0.1:6379> SMOVE myset myset2 ww #将myset中的ww移动到myset2中
(integer) 1
127.0.0.1:6379> SMOVE myset myset2 qq
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "rr"
2) "ee"
127.0.0.1:6379> SMEMBERS myset2
1) "qq"
2) "ww"

两个set的交集,并集,差集

127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key2 a e f
(integer) 3
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "a"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "c"
2) "e"
3) "b"
4) "a"
5) "f"
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "b"
2) "c"

ZSet(有序集合)

相对于set,多了一个计数位。

添加元素,排序有序集合

127.0.0.1:6379> zadd salary 2500 szw
(integer) 1
127.0.0.1:6379> zadd salary 20000 sql
(integer) 1
127.0.0.1:6379> zadd salary 10000 sq
(integer) 1
127.0.0.1:6379> zadd salary 100 syh
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #进行从小到大排序
1) "syh"
2) "szw"
3) "sq"
4) "sql"

127.0.0.1:6379> zadd salary 10 ww 1000 qq #添加多个值
(integer) 2
127.0.0.1:6379> zrange salary 0 -1
1) "ww"
2) "syh"
3) "qq"
4) "szw"
5) "sq"
6) "sql"

127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 显示所有并且带上成绩
 1) "ww"
 2) "10"
 3) "syh"
 4) "100"
 5) "qq"
 6) "1000"
 7) "szw"
 8) "2500"
 9) "sq"
10) "10000"
11) "sql"
12) "20000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 100  withscores # 显示小于等于100的
1) "ww"
2) "10"
3) "syh"
4) "100"

127.0.0.1:6379> ZREVRANGE salary 0 -1 #从高到底进行排序
1) "sql"
2) "sq"
3) "szw"
4) "qq"

移除元素

127.0.0.1:6379> ZRANGE salary 0 -1
1) "ww"
2) "syh"
3) "qq"
4) "szw"
5) "sq"
6) "sql"
127.0.0.1:6379> ZREM salary ww syh
(integer) 2
127.0.0.1:6379> ZRANGE salary 0 -1
1) "qq"
2) "szw"
3) "sq"
4) "sql"
127.0.0.1:6379> zcard salary # 获取有序集合的个数
(integer) 4

获取成员区间的成员个数

127.0.0.1:6379> zadd myset 1 aa 2 bb 3 cc
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 3 #这里可以知道从1到3有三个值
(integer) 3

在这个网站中,可以查出指令的使用方法。

用的时候多看文档!

https://redis.io/commands

三种特殊类型

Geospatita类型

地理位置,存取经度纬度,可以存取城市之间的地理位置,附近的人,直线距离可以用到的场景。

Hyperloglog类型

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5个。 基数估计就是在误差可接受的范围内,快速计算基数。

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

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

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。

使用这种数据类型的场景

传统的方式,set保存用户id,然后可以统计set的个数作为判断标准。

但是这个方式会出现大量的重复,比较麻烦,我们的目标是为了计数,而不是保存用户的id。

127.0.0.1:6379> PFADD myket a b c d
(integer) 1
127.0.0.1:6379> PFADD myket2 c d e f
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 0
127.0.0.1:6379> PFCOUNT myket
(integer) 4
127.0.0.1:6379> PFCOUNT myket2
(integer) 4
127.0.0.1:6379> PFMERGE myket3 myket2 myket
OK
127.0.0.1:6379> PFCOUNT myket3
(integer) 6

Bitmap类型

位存储

统计用户信息,活跃,不活跃。登录,未登录。打卡,未打卡。

两个状态的都可以使用bitmap位图,数据结构。

都是操作二进制位来进行记录,只用0和1。占用的内存十分小。

测试周一到周日打卡

# 1代表打卡,0代表未打卡
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 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
#获取那一天打卡的情况
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379> getbit sign 2
(integer) 1
# 统计操作,统计一周值为1(打卡)的天数
127.0.0.1:6379> bitcount sign 0 6
(integer) 5

Redis提升

事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

一个事务从开始到执行会经历以下三个阶段:

开始事务。
命令入队。
执行事务。

Redis的事务不保证原子性。

# 正常的事务
127.0.0.1:6379> MULTI # 开始事务,这时候编写的执行都不进行执行,只是放在了一块
OK
127.0.0.1:6379> set k1 a
QUEUED
127.0.0.1:6379> set k2 b
QUEUED
127.0.0.1:6379> set k3 c
QUEUED
127.0.0.1:6379> exec # 执行事务,这时候,在队列中的所有事务才开始执行
1) OK
2) OK
3) OK
# 放弃事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> set c 3
QUEUED
127.0.0.1:6379> DISCARD # 这里进行了放弃事务
OK
127.0.0.1:6379> get c
(nil)

# 在编写事务时,如果出现异常,这整个redis事务就无法执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> qwesad b 1 # 这里我们瞎写的一个命令,事务进行了报错
(error) ERR unknown command `qwesad`, with args beginning with: `b`, `1`, 
127.0.0.1:6379> set b 3
QUEUED
127.0.0.1:6379> exec # 执行事务,我们可以发现啊,整个事务没有执行,还报错了
(error) EXECABORT Transaction discarded because of previous errors.

# 在执行事务时,如果出现错误,不会发生回滚操作,可以执行的依然执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a "a"
QUEUED
127.0.0.1:6379> INCR a
QUEUED
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) ERR value is not an integer or out of range # 只有这条没有进行执行,其他两条依然执行
3) OK
127.0.0.1:6379> get a
"a"

Redis实现乐观锁(面试)

悲观锁

很悲观,认为任何时候都会出现问题,无论做什么都会加上锁。

乐观锁

很乐观,认为什么时候都不会出现问题,所以不会上锁。更新的时候去判断一下,在此期间是否有人更改过数据。

Redis实现乐观锁的步骤

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> watch money # 使用watch进行监控
OK
127.0.0.1:6379> MULTI   # 开启事务
OK
127.0.0.1:6379> DECRBy money 10
QUEUED
127.0.0.1:6379> EXEC # 执行事务,这里我们监视了money,如果另外一个客户端更改过,事务会执行不成功,我们需要unwatch解锁后,重新监视再次进行事务。
1) (integer) 90

Jedis

我们要使用Java来操作Redis

什么是redis?

是redis官方推荐的java链接开发工具,使用java操作redis的中间件。如果要使用java操作redis,对jedis应该十分熟悉!

jedis的api命令和上面的数据类型的指令一样。

原生api!!!

集成springboot(无笔记,需补上)

持久化

介绍

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中 的数据库状态也会消失。所以 Redis 提供了持久化功能!

持久化之RDB操作

什么是rdb

在主从复制中,rdb就是备用了!从机上面!

rdb文件
设置持久化保存机制

触发机制

  • save的规则满足的情况下,会自动触发rdb规则(配置的规则在配置文件中,上图就是)

  • 执行 flushall 命令,也会触发我们的rdb规则!

  • 退出redis,也会产生 rdb 文件

优点:

1、适合大规模的数据恢复!

2、对数据的完整性要不高!

缺点:

1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个后一次修改数据就没有的了!

2、fork进程的时候,会占用一定的内容空间

持久化之AOF操作

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

Aof保存的是 appendonly.aof 文件,并且默认不开启,,我们需要手动进行配置!我们只需要将 appendonly 改为yes就开启了 aof。

开启aof设置

如果这个 aof 文件有错误,这时候 redis 是启动不起来的吗,我们需要修复这个aof文件 redis 给我们提供了一个工具 redis-check-aof --fix 进行修复。

优点:

1、每一次修改都同步,文件的完整会更加好!

2、每秒同步一次,可能会丢失一秒的数据 3、从不同步,效率高的!

缺点:
1、相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!

2、Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化

扩展:

(区别)https://www.php.cn/redis/423077.html

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储

2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始 的数据,

AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重 写,使得AOF文件的体积不至于过大。

3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化。

4、同时开启两种持久化方式 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在

通常情况下AOF 文件保存的数据集要比RDB文件保存的数据集要完整。 RDB 的数据不实时,同时使用两者时服务

器重启也只会找AOF文件,那要不要只使用AOF呢?作者 建议不要,因为RDB更适合用于备份数据库(AOF在不断

变化不好备份),快速重启,而且不会有 AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够 了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自 己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的后将 rewrite 过程中产 生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重 写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也 减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据, 启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构

订阅发布

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、 微博、关注系统!
Redis 客户端可以订阅任意数量的频道。
订阅/发布消息图: 第一个:消息发送者, 第二个:频道 第三个:消息订阅者

订阅/发布消息图

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

pubsub1.png

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

pubsub2.png

演示:

发布端:
127.0.0.1:6379> PUBLISH szw qqqq
(integer) 1
127.0.0.1:6379> PUBLISH szw hhhhahhaha
(integer) 1

订阅端就会接收到来自发布端的信息:

在订阅端看到发布端的消息

发布订阅命令:(中文文档中截取)

Redis发布订阅命令

Redis集群搭建

127.0.0.1:6379> info replication # 查看当亲库的信息
# Replication
role:master # 角色
connected_slaves:0 # 从机数量为0
master_replid:863219d1eaa861a7ebc11c276849bf704b2d2308
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

环境搭建

1、复制三个redis.conf文件

2、每个文件中修改端口号

3、pidfile的名字

4、log文件的名字

5、dump.rdb的名字

端口:

修改端口号

pid文件:

pid文件

日志文件:

日志文件

rdb文件:

rdb文件

修改完毕之后,启动三个集群,可以通过进程信息查看。

ps -ef | grep redis
查看三个进程端口号

一主二从

默认情况下,每一台Redis服务器都是主节点。我们一般只用配置从机就行了。

一主(79)二从(80,81)。

127.0.0.1:6381> SLAVEOF 127.0.0.1 6379 #认主机
OK

也可以在配置文件中进行配置(配置文件中修改的是永久的,记得加密码):

配置从机

配置后重启服务器:

在主机中查看信息,可以看到配置的两个从机

我们已经看到主机端口6379已经含有两个从机!

一主二从,读写分离,主机写,从机进行读取。

我们可以发现,从机内是无法进行写操作的!

从机无法进行写入

如果主机断开连接,从机依然可以进行读。

如果主机断开后又连接上了,从机依然可以读到主机新写的内容。

复制原理

从机启动成功后,会发送一个同步sync命令给主机。

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

全量复制:从机服务在接受到数据库文件数据后,将其存盘并加载到内存中。

增量复制:主机继续将新的所有收集到的修改命令依次传给从机,完成同步。

只要是我们重新连接主机,一次完全同步将会被自动执行!

谋朝篡位

如果说主机宕机了,两个从机就群龙无首了,这时候我们可以给一个从机使用:

slavof no one

这个命令可以使从机变成主机,但是注意!如果主机复活了,主机就是光杆司令了!

哨兵模式

介绍

什么是哨兵模式?

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式

哨兵模式

配置哨兵文件:

[root@iZ2zeeqh1fctjw2bhzgjnaZ redis]# vim sentinel.conf
# 配置文件中的内容
sentinel monitor myredis 127.0.0.1 6379 1

配置文件中的内容最后那个1,代表如果主机挂掉了,哨兵就会通过投票来选举让谁来接替成主机,票数最多的,就是主机!

# 启动我们自己配置的哨兵
[root@iZ2zeeqh1fctjw2bhzgjnaZ bin]# /usr/local/redis/bin/redis-sentinel /usr/local/redis/sentinel.conf

可以看到两个从节点

我们把自己主机6379关闭,等待30s。

关闭6379后,发生变化
查看状态,主机变化为6380

通过以上可以看到,主机已经换成了6380端口。

如果master出现了故障,哨兵会根据算法,投票,选择出一个新的主机。就算6379回来了,也只能作为6380的从机!

优点

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

缺点

  • Redis不好在线扩容,集群一旦达到上线,在线扩容就十分麻烦!
  • 实现哨兵模式的配置是十分麻烦的,里面有很多选择!

哨兵模式的配置

不止上面那一个。

Redis缓存穿透和雪崩

参考:https://blog.csdn.net/kongtiao5/article/details/82771694

面试高频!!!!

缓存穿透

概念

就是用户想要查询一个数据,发现redis内存中不存在,也就是说缓存没有命中,就去持久层数据库中进行查找,发现也没有。当很多用户进行查找时,缓存都没有命中,于是都去请求数据库,这会给数据库造成压力,这时候就会出现缓存穿透。

缓存穿透

方案解决1:

添加布隆过滤器。

方案解决2:

需要查询的那个对象设置为空的。

缺点:

  • 如果把空值存起来的话,意味着存储更多的空字符串。
  • 即使设置了过期时间,还是会和存储层有一段时间窗口内的数据不一致,这对于需要保持一致性的业务会有影响。

缓存击穿

概念

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁参考代码如下

缓存雪崩

概念

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

你可能感兴趣的:(Redis)