首页 > 数据库 >Redis

Redis

时间:2024-10-25 18:20:11浏览次数:5  
标签:127.0 6379 Redis redis 命令 key

常见面试题:

  • Redis简介
  • 为什么要用Redis?
  • Redis的优点
  • Redis的应用场景
  • Linux Centos7安装Redis与Window10安装Redis
  • Redis客户端介绍与使用
  • Redis核心配置文件详解
  • Redis Key命名规范
  • Redis最常用的5种数据类型及其对应的命令
  • Redis的性能测

1 什么是Redis?

  1. Redis是一个由C语言编写的,开源的key-value存储系统,它支持存储的value类型相对更多,包括String、List、Set、Sorted Set和Hash,而且这些操作都是原子性的,又被其作者戏称为数据结构服务器。
  2. 它属于非关系型数据库NoSQL(Not Only SQL)数据库中的键值(Key-Value)存储数据库,即它属于与MySQL和Oracle等关系型数据库不同的非关系型数据库。它与memcached类似,但是优于memcached。
  3. 为了保证效率,数据都是缓存在内存中。Redis可以周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

为什么使用Redis?

  • 假如我们不使用Redis,那么用户每次发送请求的时候,服务器都会前往关系型数据库(MySQL,Oracle…)查询数据(不考虑其他缓存技术),每次往返数据库会消耗大量系统资源。如果用户并发量高的话,可能服务器就承受不了,就会宕机崩溃。

  •  如果我们使用了Redis,那么用户每次发送请求的时候,先去查询Redis缓存,如果缓存中有想要的数据,就直接响应客户端。如果没有,再去查询关系型数据库,这样就能节省系统开销,如果再搭建Redis集群,那么就能应对大量的高并发场景。

Redis的优点

Redis之所以被广泛应用在缓存领域,则是因为它的优点:

  • Redis是一款性能极高的非关系型数据库,Redis读的速度是110000次/s,写的速度是81000次/s,非常适合做缓存。
  • 丰富的数据类型,Redis支持String、List、Set、Sorted Set和Hash数据类型操作,从而支持大量的应用场景。
  • 原子性,Redis所有操作都是原子性的,不用担心并发安全问题,同时Redis还支持对几个操作全并后的原子性执行。
  • Redis还支持publish/subscribe,通知,key过期等特性。

Redis的常见应用场景

  • 1缓存:通过内存存储来加速数据访问。
    • 并发量高的网站都使用了Redis作为缓存层的实现,缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。
    • Redis提供了键过期功能(适用存储手机验证码),也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。
  • 2会话存储:用户登录信息的存储。
    • 集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,
    • 一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。
  • 3消息队列:配合列表和发布订阅模式实现高效消息队列。
    • 消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。
    • Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,Redis不能和专业的消息中间件相比。
  • 4分布式锁:使用Redis的原子操作特性实现锁机制。

 

    • 在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,

 

 

    • 如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,

 

 

    • 利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。

 

 

    • 可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。
  • 5计数器:统计网站数据。
    • 什么是计数器?如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。
    • Redis提供的incr命令(自增命令)来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。
  • 6社交网络
    • 点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。
  • 7最新列表:
    • redis列表(list)结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。

 

2 Redis的安装与环境配置

一、zip压缩包方式下载安装

1 在linux环境下Redis可以直接通过源码编译安装。Windows下编译一般不那么方便,我们下载已经编译好的.zip压缩包来安装。

2 Window版本的Redis下载地址:https://github.com/MicrosoftArchive/redis/release

 

 

 3 将下载的压缩包解压到指定的文件夹中,如:D:\Redis,内容如下:

 

4启动Redis服务 :在Redis的安装目录下打开cmd窗口,然后执行命令来启动服务:

 

redis-server.exe redis.windows.conf

 注意必须在redis文件目录下执行上面的命令

cd 切换目录命令示例:

  • 例:cd // 显示当前目录
  • 例:cd … // 进入父目录
  • 例:cd /d d: // 进入上次d盘所在的目录(或在直接输入:d:)
  • 例:cd /d d:\ // 进入d盘根目录
  • 例:cd d: // 显示上次d盘所在的目录
  • 例:cd /d d:\src // 进入d:\src目录
默认端口为6379,出现图上的图标说明redis服务启动成功。命令里面的 redis.windows.conf 可以省略,省略后,使用redis-server.exe命令会使用默认的配置。

 

为了方便,建议把Redis路径配置到系统变量Path值中,这样就省得再输路径了。

5、打开Redis客户端进行连接

 我们使用redis-cli.exe命令来打开Redis客户端:

redis-cli.exe -h 127.0.0.1 -p 6379

 

 在命令中输入ping命令来检测redis服务器与redis客户端的连通性,返回PONG则说明连接成功了。

如果出现连接不成功,注意服务打开以后,另启一个 cmd 窗口到Redis所在的目录执行命令,原来的Redis启动窗口不要关闭,不然就无法访问服务端了。

 如果连接成功,到此Redis的安装和部署也就完成了。

6、使用一些基础操作来测试

Redis默认拥有16个数据库,初始默认使用0号库,在命令行中通过select命令将数据库切换到8号数据库:

select 8 

 在命令中通过set命令设置键值,通过get命令取出键值:

 在命令中通过shutdown命令来关闭redis服务:

 在Redis服务启动的cmd窗口中会出现服务关闭的提醒,如下图:

 

Redis常用的服务指令

卸载服务:redis-server --service-uninstall

开启服务:redis-server --service-start

停止服务:redis-server --service-stop

3. Redis核心配置文件(redis.conf)

配置 作用  默认
port  Redis访问端口,由于Redis是单线程模型,因此单机开多个Redis进程的时候会修改端口 6379
bind  如果bind选项为空或0.0.0.0的话,那会接受所有来自于可用网络接口的连接 127.0.0.1
timeout  指定在一个client空闲多少秒之后就关闭它,0表示不管  0
daemonize  指定Redis是否以守护进程的方式启动  no
logfile  配置log文件地址,默认打印在命令行终端的窗口上 “”
databases  设置Redis数据库的数量,默认使用0号DB 16
save  把Redis数据保存到磁盘上,这个是在RDB的时候用的 save 900 1,save 300 10,save 60 10000
dbfilename dump的文件位置 dump.rdb
dir Redis工作目录 ./
slaveof 主从复制,使用slaveof让一个节点称为某个节点的副本,这个只需要在副本上配置 关闭
masterauth 如果主机使用了requirepass配置进行密码保护,使用这个配置告诉副本连接的时候需要鉴权 关闭
slave-read-only 配置Redis的Slave实例是否接受写操作,即Slave是否为只读Redis yes
requirepass 设置客户端认证密码 关闭
maxclients 设置同时连接的最大客户端数量,一旦达到了限制,Redis会关闭所有的新连接并发送一个"max number of clients reached"的错误 关闭,默认10000
maxmemory 不要使用超过指定数量的内存,一旦达到了,Redis会尝试使用驱逐策略来移除键 关闭
appendonly 是否开启AOF no
appendfilename AOF文件名称 appendonly.aof
requirepass 设置客户端认证密码 关闭

4. Redis Key命名规范

  • Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,比如"me"的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。
  • key不要太长,最好不要操作1024个字节,这不仅会消耗内存还会降低查找效率。
  • key不要太短,如果太短会降低key的可读性。
  • 在项目中,key最好有一个统一的命名规范(根据企业的需求),我们可以使用分段设计法来设计Key名称。

分段设计法

使用冒号把Key中要表达的多种含义分开表示,步骤如下:

  1. 把表名(实体类名称)转化为Key前缀
  2. 主键名(或其他常用于搜索的字段)
  3. 主键值
  4. 存储的字段名(属性名称)

 

5 Redis最常用的5种数据类型及其对应的命令

 Redis最强大的功能之一在于它支持多种数据结构,而不仅仅是键值对。让我们来深入了解这些数据结构及其用法。

上面说了Redis是Key-Value结构的非关系型数据库,其中Key只能是字符串(String),Value有多种数据结构:

Redis命令

  • 卸载服务:redis-server --service-uninstall
  • 开启服务:redis-server --service-start
  • 停止服务:redis-server --service-stop

Redis key命令

  • keys pattern:查找所有符合给定模式pattern的key,匹配模式有如下几种:
    • *:匹配所有key
    • ?:匹配单个key
  • exists key:检查给定key是否存在
    • 存在返回1
    • 不存在返回0
  • randomkey:从当前数据库中随机返回(不删除)一个 key
    • 当数据库不为空时,返回一个 key
    • 当数据库为空时,返回 nil
  • del key [key…]:删除给定的一个或多个Key,不存在的key会被忽略
  • expire key seconds:为给定key设置有效时间,以秒为单位,当key过期时(有效时间为0),它会被自动删除
    • 设置成功返回 1
    • 当key不存在或者不能为key设置生存时间时返回0
  • ttl key:以秒为单位,返回给定key的剩余生存时间(TTL, time to live)
    • 当key不存在时,返回-2
    • 当key存在但没有设置剩余生存时间时,返回-1
    • 否则,以秒为单位,返回key的剩余生存时间
  • persist key:移除给定key的有效时间
    • 当生存时间移除成功时,返回1
    • 如果key不存在或key没有设置生存时间,返回0
    • rename key newkey:将 key 改名为 newkey
    • 当key和newkey相同,或者key不存在时,返回一个错误
    • 当newkey已经存在时, rename命令将覆盖旧值
  • type key:返回key的类型
  • select 数据库编号:(默认有16个数据库,0-15)
  • move key 数据库编号:将当前数据库key移动到某个数据库,目标库有,则不能移动

Redis String命令

  • String类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这 便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。
  • 在Redis中字符串类型的Value最多可以容纳的数据长度是512M。
  • Redis String命令是操作value为String类型的命令。

set key value:设定key持有指定的字符串value,如果该key存在则进行覆盖 操作。总是返回”OK”
get key:获取key的value。如果与该key关联的value不是String类型,redis将返回错误信息,因为get命令只能用于获取String 类型的值;如果该key不存在,返 回null。

127.0.0.1:6379[1]> set user:id:1:name xq
OK
127.0.0.1:6379[1]> get user:id:1:name
"xq"
127.0.0.1:6379[1]> set user:id:1:name xxxq
OK
127.0.0.1:6379[1]> get user:id:1:name
"xxxq"

getset key value:先获取该key的值,然后在设置该key的值。

127.0.0.1:6379[1]> getset user:id:1:name zwq
"xxxq"
127.0.0.1:6379[1]> get user:id:1:name
"zwq"

incr key:将指定的key的value原子性的递增1.如果该key不存在,默认初始值为0,在incr之后其值为1。如果value的值不能转成整型,如"xq",该操作将执行失败并返回相应的错误信息。

decr key:将指定的key的value原子性的递减1.如果该key不存在,默认初始值为0,在incr之后其值为-1。如果value的值不能转成整型,如"xq",该操作将执行失败并返回相应的错误信息。

127.0.0.1:6379[1]> set user:id:1:age 22
OK
127.0.0.1:6379[1]> incr user:id:1:age
(integer) 23
127.0.0.1:6379[1]> decr user:id:1:age
(integer) 22

 

incrby key increment:将指定的keyvalue原子性增加increment。如果该key不存在,默认初始值为0,在incrby之后,该值为increment。如果该值不能转成整型,如"xq"则失败并返回错误信息。
decrby key decrement:将指定的keyvalue原子性减少decrement,如果该key不存在。默认初始值为0,在decrby之后,该值为decrement。如果该值不能转成整型,如"xq"则失败并返回错误信息。

127.0.0.1:6379[1]> incrby user:id:1:height 5
(integer) 5
127.0.0.1:6379[1]> decrby user:id:1:height 3
(integer) 2

 

append key value:如果该key存在,则在原有的value后追加该值;如果该key不存在,则重新创建一个key/value。

127.0.0.1:6379[1]> append user:id:1:address guangdong
(integer) 9
127.0.0.1:6379[1]> append user:id:1:address guangzhou
(integer) 18
127.0.0.1:6379[1]> get user:id:1:address
"guangdongguangzhou"

 

Redis Hash命令

  • Redis中的Hashes类型可以看成具有String Key和String Value的map容器。
  • 所以该类型非常适合于存储值对象的信息。如Username、Password和Age等。
  • 如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash可以存储4294967295个键值对。

hset key field value:为指定的key设定field/value对(键值对)。

hget key field:返回指定的key中的field的值。

hgetall key:获取key中的所有filed-vaule

127.0.0.1:6379[1]> hset user:id:2 name zwq
(integer) 1
127.0.0.1:6379[1]> hset user:id:2 age 22
(integer) 1
127.0.0.1:6379[1]> hget user:id:2 name
"zwq"
127.0.0.1:6379[1]> hgetall user:id:2
1) "name"
2) "zwq"
3) "age"
4) "22"

 

hmset key fields:设置key中的多个filed/value。

hmget key fileds:获取key中的多个filed的值。

hexists key field:判断指定的key中的filed是否存在。

hlen key:获取key所包含的field的数量。

hincrby key field increment:设置key中filed的值增加increment,如:age增加20。
127.0.0.1:6379[1]> hmset user:id:3 name zs age 18
OK
127.0.0.1:6379[1]> hmget user:id:3 name age
1) "zs"
2) "18"
127.0.0.1:6379[1]> hlen user:id:3
(integer) 2
127.0.0.1:6379[1]> hexists user:id:3 age
(integer) 1
127.0.0.1:6379[1]> hincrby user:id:3 age 10
(integer) 28

Redis List命令

  • 在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不 存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是 4294967295。
  • 从元素插入和删除的效率来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在毫秒级内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。

lpush key value1 value2…:在指定的key所关联的list的头部插入所有的values,如果该key不存在,该命令在插入的之前创建一个与该key关联的空链表,之后再向该链表的头部插入数据。插入成功,返回元素的个数。
rpush key value1、value2…:在该list的尾部添加元素。
lrange key start end:获取链表中从start到end的元素的值,start、end可为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推…

127.0.0.1:6379[1]> lpush grade:7 class1 class2 class3
(integer) 3
127.0.0.1:6379[1]> rpush grade:7 class6 class5 class4
(integer) 6
127.0.0.1:6379[1]> lrange grade:7 0 5
1) "class3"
2) "class2"
3) "class1"
4) "class6"
5) "class5"
6) "class4"

 

lpushx key value:将一个值插入到已存在的列表头部。如果列表不在,操作无效。

rpushx key value:一个值插入已存在的列表尾部(最右边)。如果列表不在,操作无效。

key不存在,插入失败
127.0.0.1:6379[1]> lpushx grade:8 class7
(integer) 0
127.0.0.1:6379[1]> rpushx grade:8 class8
(integer) 0
  • lpush key value [value …] :将一个或者多个值插入到列表key的表头
  • rpush key value [value …] :将一个或者多个值插入到列表key的表尾
  • lpushx key value :当且仅当key存在时将值value插入到key的表头
  • rpushx key value: 当且仅当key存在时将值value插入到key的表尾
  • lpop key: 移除并返回列表key的头元素
  • rpop key: 移除并返回列表的尾元素
  • blpop key [key …] timeout:  lpop的阻塞式指令
  • brpop key [key …] timeout : rpop的阻塞式指令
  • rpoplpush source destination: 将列表Source的尾元素弹出以及返回客户端,并且将该元素插入到destination列表中
  • brpoplpush source destination: timeout rpoplpush的阻塞版
  • lindex key index: 返回key中下表为index的元素
  • linsert key before:    after pivot value
  • llen key: 返回列表key的长度
  • lrange key start stop: 返回列表key中指定区间内的元素
  • lrem key count value: 根据count的值,移除列表中与参数value相等的元素
  • lset key index value: 将列表key下表为index的元素值设置为value,0代表链表的头元素,-1代表链表的尾元素
  • ltrim key start stop: 对一个列表进行trim

Redis Set命令

  • 在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。Set可包含的最大元素数是4294967295。
  • 和List类型不同的是,Set集合中不允许出现重复的元素。和List类型相比,Set类型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Set之间的聚合计算操作,如unions、intersections和differences。由于这些操作均在服务端完成, 因此效率极高,而且也节省了大量的网络IO开销。

 

  • sadd key member [member …]    将元素加入到集合key中,已经有的忽略
  • scard key       返回集合key的元素个数
  • sdiff key [key …]    返回一个集合的全部成员,该集合是所有给定集合之间的差集
  • sdiffstore destination key [key …]  返回集合之间的差集,并将它保存在destination集合中
  • sinter key [key …] 返回集合中给定集合的交集
  • sinterstore destination key [key …] 返回给定集合之间的差集,并将它保存在destination集合中
  • sismember key member 判断member元素是否为集合key成员
  • smembers key    返回集合中的所有成员
  • smove source destination member   将member元素从source集合移动到destination集合
  • spop key   移除并返回集合中的一个随机元素
  • srandmember key [count]   返回指定count个数的集合,count为正数表示不能重复,负数可以重复
  • srem key member [member …]   移除集合key中的多个元素
  • sunion key [key …]   返回所有指定key的并集
    127.0.0.1:6379> sadd names zs ls ww zl
    (integer) 4
    127.0.0.1:6379> sadd namess zs zl
    (integer) 2
    127.0.0.1:6379> sinter names namess
    1) "zl"
    2) "zs"

Redis Sorted Set(有序集合)命令

 

  • Sorted Set和Set类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score) 却是可以重复的。
  • 在Sorted Set中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted Set中的成员在集合中的位置是有序的,因此, 即便是访问位于集合中部的成员也仍然是非常高效的。事实上,Redis所具有的这一特征在很多其它类型的数据库中是很难实现的,换句话说,在该点上要想达到和Redis同样的高效,在其它数据库中进行建模是非常困难的。

  • zadd key source member [[source member] […]] 将一个或者多个member元素及其score值加入到有序集合key中
  • zcard key 返回有序集合key的元素个数
  • zcount key min max 返回有序集合key中,score值在min和max之间的元素个数
  • zincrby key increment member 为有序集合key的成员member的score值加上增量increment
  • zrange key start stop 返回有序集key中,指定下标区间内的成员
  • zrevrange key start stop [withscores] 返回指定区间内的成员递减顺序
  • zrangebyscore key min max [withscopes] [limit offset count] 返回score值介于min和max之间的集合
  • zrank key member 返回有序集key中成员member的排名
  • zrevrank key member 返回有序集key中成员member的递减排名
  • zrem key member [member …] 移除有序集key中的多个成员
  • zremrangebyrank key start stop 移除有序集key中,指定排名区间内的所有成员
  • zremrangebyscore key min max 移除有序集key中,指定score范围内的成员
  • zscore key member 返回成员member的score值
    127.0.0.1:6379> zadd mathScore 98 zs 97 ls 76 ww 100 zl
    (integer) 4
    127.0.0.1:6379> zcard mathScore
    (integer) 4
    127.0.0.1:6379> zscore mathScore ww
    "76"

5 Redis的性能测试

Redis在安装之后有一个redis-benchmark,这个就是Redis提供用于做性能测试的,它可以用来模拟N个客户端同时发出M个请求。首先看一下redis-benchmark自带的一些参数:

命令  作用  默认值

  • -h   服务器名称  127.0.0.1
  • -p   服务器端口  6379
  • -s   服务器Socket   无
  • -c   并行连接数   50
  • -n   请求数   10000
  • -d   SET/GET值的字节大小   2
  • -k   1表示keep alive,  0表示重连 1
  • -r   SET/GET/INC使用随机Key而不是常量,在形式上key样子为mykey_ran:000000012456,-r的值决定了value的最大值   无
  • -p   使用管道请求      1,即不使用管道
  • -q   安静模式,只显示query/sec值    无
  • –csv \使用csv格式输出      无
  • -l     循环,无限运行测试   无
  • -t   只运行使用逗号分割的命令的测试    无
  • -I   空闲模式,只打开N个空闲线程并且等待   无

redis-benchmark -q,运行该命令,测试QPS(每秒查询率),从测试结果来看,值为40000-50000之间,离官方说的每秒100000QPS还是有一定差距,当然这也和电脑配置有关。

D:\Redis>redis-benchmark.exe -q
PING_INLINE: 44883.30 requests per second
PING_BULK: 45641.26 requests per second
SET: 37105.75 requests per second
GET: 41237.11 requests per second
INCR: 44208.66 requests per second
LPUSH: 42625.75 requests per second
LPOP: 44702.73 requests per second
SADD: 44863.16 requests per second
SPOP: 44822.95 requests per second
LPUSH (needed to benchmark LRANGE): 44622.94 requests per second
LRANGE_100 (first 100 elements): 23126.73 requests per second
LRANGE_300 (first 300 elements): 12220.46 requests per second
LRANGE_500 (first 450 elements): 9390.55 requests per second
LRANGE_600 (first 600 elements): 7499.63 requests per second
MSET (10 keys): 36941.26 requests per second

 

在实际项目中,Redis存储的Key和Value应该是数量丰富的,上面命令只是测试单一的key和单一的Value,我们测试使用-r模拟value到100000且将运行次数提高到1000000次,具体命令为redis-benchmark -q -r 100000 -n 1000000,从测试结果来看,和上面测试的差不多,说明Redis读写能力在Key和Value是否丰富上没多大关系,还是和电脑配置和网络带宽有关些。

D:\Redis>redis-benchmark -q -r 100000 -n 1000000
PING_INLINE: 45968.56 requests per second
PING_BULK: 47279.09 requests per second
SET: 40817.99 requests per second
GET: 44265.42 requests per second
INCR: 44064.51 requests per second
LPUSH: 44724.72 requests per second
LPOP: 44974.14 requests per second
SADD: 41211.62 requests per second
SPOP: 45091.76 requests per second
LPUSH (needed to benchmark LRANGE): 47034.48 requests per second
LRANGE_100 (first 100 elements): 24550.12 requests per second
LRANGE_300 (first 300 elements): 12818.54 requests per second
LRANGE_500 (first 450 elements): 9410.61 requests per second
LRANGE_600 (first 600 elements): 7476.92 requests per second
MSET (10 keys): 31666.61 requests per second

6 Redis Java客户端介绍

Redis Java客户端主要有三种:Jedis、Lettuce、Redisson。

Jedis:

  1. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持。
  2. Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。
  3. Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
  4. Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。

Lettuce:

  1. Lettuce是高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。目前springboot默认使用的客户端。
  2. Lettuce基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。

Redisson:

  1. Redisson实现了分布式和可扩展的Java数据结构,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列。和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
  2. Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用。
  3. Redisson基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。
  4. Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。77

7 Jedis操作Redis

上面介绍了三种Redis Java客户端,接下来就使用Jedis操作Redis,首先引入Jedis依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>

在使用Jedis的时候,有两种方式可选:

  1. 单机版Jedis,对应redis.clients.jedis.Jedis类。
  2. Jedis连接池,对应redis.clients.jedis.JedisPoolConfig类。

单机版Jedis

单机版的Jedis就是一旦与Redis建立连接成功后,然后进行一系列对Redis的操作之后,就要关闭Redis连接对象,否则会占用系统资源。

@Test
public void jedis(){
    Jedis jedis = null;
    try {
        jedis = new Jedis("localhost",6379);//设置地址和端口
//            jedis.auth(PASSWORD);//如果redis服务器配置了需要密码,此处必须设置
        jedis.select(5);//选择指定数据库
        //存储集合到redis,并取出
        jedis.lpush("names","xxxq","xxq","xq");
        System.out.println(jedis.lrange("names", 0, -1));
    }finally {
        jedis.close();
    }
}

Jedis连接池

以前我们使用MySQL、Oracle等关系型数据库的时候,都会使用数据库连接池,比如:C3P0、Druid等等。在Jedis中,也提供了一个Redis连接池来管理Redis连接对象,就是redis.clients.jedis.JedisPool,还有一个对Jedis连接池配置的类:redis.clients.jedis.JedisPoolConfig,下面我们对它进行配置。

  • 配置连接池需要一些参数,把这些参数单独放在一个配置文件中:redis.properties
    redis.host = xxx
    redis.port = 6379
    redis.password = xxx
    redis.timeout = 5000
    redis.maxTotal = 100
    redis.maxIdle = 20
    redis.minIdle = 5
  • 下面封装一个Jedis连接池工具类,简化后续的开发工作。

 

public class JedisPoolUtils {
    private static JedisPool  pool = null;

    /**
     *  读取属性配置文件,配置Jedis连接池
     */
    static {
        Properties properties = new Properties();
        try {
            properties.load(JedisPoolUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        int maxIdle = new Integer(properties.getProperty("redis.maxIdle"));
        int minIdle = new Integer(properties.getProperty("redis.minIdle"));
        int maxTotal = new Integer(properties.getProperty("redis.maxTotal"));
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxTotal(maxTotal);
        //设置主机
        String host = properties.getProperty("redis.host");
        //设置端口号
        int port = new Integer(properties.getProperty("redis.port"));
        //创建Jedis连接池
        pool = new JedisPool(poolConfig,host,port);
    }
    
    /**
        获取单机版Jedis
    */
    public static Jedis getJedis(){
        return pool.getResource();
    }
}
  • 使用Jedis连接池创建单机版Jedis,测试是否配置成功。使用Jedis连接池,我们不需要手动关闭Jedis连接对象。
    public class JedisPoolTest{
        public static void main(String[] args) {
            Jedis jedis = JedisPoolUtils.getJedis();
            System.out.println("Redis服务正在运行:"+jedis.ping());
            //set命令
            jedis.set("name", "zwq");
            System.out.println(jedis.get("name"));
        }
    }
    
    执行结果:
        Redis服务正在运行:PONG
        zwq

 

8.SpringData-Redis操作Redis

Jedis支持五种数据类型(string/hash/list/set/zset/)的操作,但在Java中我们却通常以类对象为主,所以在实际项目中,我们需要将Redis存储的五种数据类型与Java对象之间进行切换,我们可以自己编写一些工具类,实现两者之间的转换,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,就操作对象而言,使用原生的Jedis还是挺难的,好在Spring对这些进行了封装和支持,也就是SpringData-Redis。

  • 引入SpringData-Redis依赖
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.2.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.1.0</version>
    </dependency> 
  • 接下来我们使用Spring配置Jedis连接池。
    #redis.properties配置文件
    redis.host = xxx
    redis.port = 6379
    redis.password = xxx
    redis.timeout = 5000
    redis.maxTotal = 100
    redis.maxIdle = 20
    redis.minIdle = 5
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p" 
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <!--Redis服务地址-->
            <property name="hostName" value="${redis.host}"/>
            <!--端口号-->
            <property name="port" value="${redis.port}"/>
            <!--如果有密码则需要配置密码-->
            <!--<property name="password" value="${redis.password}"/>-->
            <!--连接池配置-->
            <property name="poolConfig" ref="poolConfig"/>
        </bean>
    
        <bean id="redisTemplate"
              class="org.springframework.data.redis.core.RedisTemplate"
              p:connection-factory-ref="connectionFactory"/>
    
        <!--加载属性文件-->
        <context:property-placeholder location="classpath:redis.properties"/>
    
        <!--Spring整合配置,连接池配置-->
        <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
            <!--最大连接数-->
            <property name="maxTotal" value="${redis.maxTotal}"/>
            <!--最大空闲数-->
            <property name="maxIdle" value="${redis.maxIdle}"/>
            <!--最大等待时间-->
            <property name="maxWaitMillis" value="20000"/>
            <!--最小空闲数-->
            <property name="minIdle" value="${redis.minIdle}"/>
        </bean>
    </beans>
  • 上面配置了RedisTemplate,因为普通的Redis连接对象没有办法直接将Java对象直接存入Redis内存中,我们需要替代的方案:将对象序列化(可以简单的理解为实现Serializable接口)。我们可以把对象序列化之后存入Redis缓存中,然后在取出的时候又通过转换器,将序列化之后的对象反序列化回对象,这样就完成了我们的要求。RedisTemplate可以帮助我们完成这份工作,它会找到对应的序列化器去转换Redis的键值。
  • 创建用户实体类,实现Serializable接口 进行序列化。
    @Data
    public class User implements Serializable {
        private String name;
        private Integer age;
    }
  • 测试Spring配置是否成功。

    @Test
    public void springDataRedis(){
        ApplicationContext context = new ClassPathXmlApplicationContext("redis.xml");
        RedisTemplate template = context.getBean(RedisTemplate.class);
        User user = new User();
        user.setName("zhangsan");
        user.setAge(22);
        template.opsForValue().set("zhangsan",user);
        User u = (User) template.opsForValue().get("zhangsan");
        System.out.println(u);
    }
    运行结果:
        User(name=zhangsan, age=22)
  • 从运行结果来看,SpringData-Redis已经自动帮我们序列化对象与反序列化对象了,使用起来非常简便。但是当我们查看Redis服务器的时候,发现存入的数据乱码了,如下图所示:

  • 上面说了RedisTemplate通过序列化器来转换Redis的键值,而RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此序列化器完成的。出现上面问题说明就是JDK序列化器在序列化与反序列化时编码问题,我们可以更改RedisTemplate序列化器,除了JDK序列化器JdkSerializationRedisSerializer,还有一个字符串序列化器StringRedisSerializer,因为Redis的key都是字符串类型,所以我们将key的序列化器改为StringRedisSerializer,而Value使用默认的JdkSerializationRedisSerializer

     

  • 接下来按照上面总结的原因来更改上面配置文件:redis.xml

    <bean id="redisTemplate"
                  class="org.springframework.data.redis.core.RedisTemplate"
                  p:connection-factory-ref="connectionFactory"
                p:keySerializer-ref="stringSerializer"
                p:valueSerializer-ref="jdkSerializer"/>
        
    <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" id="stringSerializer"/>
    <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" id="jdkSerializer"/>

     从上图来看,修改序列化器之后,key已经不再乱码了,但是值又乱码了。按照上面的分析可以将value的序列化器改为StringRedisSerializer就不会乱码了。

9.SpringBoot操作Redis

上面我们已经介绍了原生Jedis操作Redis与SpringData-Redis操作Redis,虽然没有讲大量的API,但是我们已经学会怎么使用了,剩下的就是大家自己来操作API实现效果了。接下来再介绍在SpringBoot中如何操作Redis。不管是SpringData-Redis,还是在SpringBoot中,都是通过RedisTemplate操作Redis,只不过SpringBoot做了更多的封装,让我们操作Redis更加简便。接下来先介绍RedisTemplate

RedisTemplate

  • RedisTemplate只是提供操作Redis的模板API,底层还是使用Redis Java客户端来实现。

  • 这里说一个细节,SpringBoot2.0之前默认是使用Jedis客户端底层实现,SpringBoot2.0之后就改为使用Lettuce客户端底层实现,如果需要在SpringBoot2.0之后使用Jedis,则需要排除Lettuce依赖,如下所示:
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  • 接下来配置Redis连接池,SpringBoot省去了繁杂的XML配置,直接在application.properties配置即可。
    #redis配置
    #Redis服务器地址
    spring.redis.host=127.0.0.1
    #Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    #Redis数据库索引(默认为0)
    spring.redis.database=0  
    #连接池最大连接数(使用负值表示没有限制)
    spring.redis.jedis.pool.max-active=50
    #连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.jedis.pool.max-wait=3000ms
    #连接池中的最大空闲连接
    spring.redis.jedis.pool.max-idle=20
    #连接池中的最小空闲连接
    spring.redis.jedis.pool.min-idle=2
    #连接超时时间(毫秒)
    spring.redis.timeout=5000ms
  • 编写测试代码,测试配置是否成功。
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class AppTest {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void test(){
            stringRedisTemplate.opsForValue().set("name","zwq");
            System.out.println(stringRedisTemplate.opsForValue().get("name"));
        }
    }
    运行结果:
        zwq
  • 上面使用的是StringRedisTemplate,它继承了RedisTemplate,从名字看它就是支持Value为String类型的读写操作,也就是Value使用了StringRedisSerializer序列化器,截图验证:

  • 我们验证读写对象会不会出现之前使用SpriingData-Redis的乱码问题,编写一个用户实体类
    @Data
    public class User implements Serializable {
        private String name;
        private Integer age;
    }
  • 上面都已经配置好了,所以直接编写测试代码即可。
    @Test
    public void saveUser(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(22);
        redisTemplate.opsForValue().set("zhangsan",user);
        User zhangsan = (User) redisTemplate.opsForValue().get("zhangsan");
        System.out.println(zhangsan);
    }
    运行结果:
        User(name=zhangsan, age=22)
  • 在程序代码中读写也是没问题,但是写入到Redis服务器上就乱码了。

  • 这个原因也是序列化与反序列化过程中产生的问题,接下来开始指定序列化器。在SpringBoot中没有了xml配置,所以需要编写一个配置类来配置序列化器,如下代码:
    @Configuration
    public class RedisCnfig {
    
        @Autowired
        RedisConnectionFactory factory;
    
        @Bean
        public RedisTemplate<Object, Object> redisTemplate() {
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setConnectionFactory(factory);
            //StringRedisSerializer序列化器
            StringRedisSerializer stringRedisSerializer =new StringRedisSerializer();
            //JdkSerializationRedisSerializer序列化器
            JdkSerializationRedisSerializer jdkRedisSerializer = new JdkSerializationRedisSerializer();
            redisTemplate.setValueSerializer(jdkRedisSerializer);
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    }
  • 再执行上面测试代码,发现key已经不乱码了,而Value还是出现乱码。
  • 通过把Value的序列化器修改为JSON序列化器,也就是对象与JSON之间的转换,这样就可以解决乱码的问题。
    @Configuration
    public class RedisCnfig {
    
        @Autowired
        RedisConnectionFactory factory;
    
        @Bean
        public RedisTemplate<Object, Object> redisTemplate() {
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setConnectionFactory(factory);
            //StringRedisSerializer序列化器
            StringRedisSerializer stringRedisSerializer =new StringRedisSerializer();
            //JdkSerializationRedisSerializer序列化器
            JdkSerializationRedisSerializer jdkRedisSerializer = new JdkSerializationRedisSerializer();
            //Json序列化器
            GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    //        redisTemplate.setValueSerializer(jdkRedisSerializer);
            redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    }
  • 除了改配置之外,还需要引入下面依赖。
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.5.4</version>
    </dependency>
  • 再执行上面的测试代码,发现Key、Value都不会乱码了。

     

10.Redis事务

事务这个词我们应该不陌生了,我们在使用MySQL或者Oracle过程中,都会开启事务,直到SQL执行完毕才会关掉事务。
可以说,事务是一种保证数据安全的机制。在关系型数据库中,事务是一组SQL的集合,要么全部执行成功,要么全部执行失败。
而在Redis中,事务是一组命令的集合,这一组命令会按照顺序在隔离的环境中执行,不受其他客户端发送来的命令打断。

下面给出官方解说

  • 事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作,事务中的命令要么全部被执行,要么全部都不执行。
  • 事务可以一次执行多个命令。MULTI、EXEC、DISCARD和WATCH是Redis事务相关的命令。
  • 可以把Redis事务看成四种阶段:开启事务,命令入队,执行事务,关闭事务。

开启事务:

  1. multi命令的执行标志着事务开启。
    127.0.0.1:6379> multi
    OK 
  2. multi命令可以将执行该命令的客户端从非事务状态切换至事务状态。

命令入队:

  1. 当Redis客户端处于非事务状态时,这个客户端的命令会立即被服务器执行。
  2. 当Redis客户端切换到事务状态之后,服务器会根据这个客户端发来的命令做出不同的响应。
  3. 如果客户端发送的命令是MULTI、EXEC、DISCARD和WATCH这四个命令中的一个,那么服务器会立即执行这个命令。
  4. 如果客户端发来的命令不是这四个命令,那么服务器不会立即执行这个命令,而是将这个命令放在一个事务队列中,然后向客户端返回QUEUED,表明这个命令被放入事务队列中了。
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set name xxxq
    QUEUED

     

事务队列:

  1. 事务队列是一个以先进先出(FIFO)的方式保存入队的命令,较先入队的命令放在队列的前面,较后入队的命令放在队列的后面。
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set name xxxq
    QUEUED
    127.0.0.1:6379> get name
    QUEUED
    127.0.0.1:6379> set author zwq
    QUEUED
    127.0.0.1:6379> get author
    QUEUED
  2. 服务器将会为上面所执行的命令创建一个队列,如下图所示:

     

执行事务:

  1. 当一个处于事务状态的客户端向服务器发送EXEC命令时,服务器会立即执行这个命令,然后执行事务队列中保存的所以命令,最后将执行命令的结果返回给客户端。当返回结果给客户端的时候,这个事务也就被关闭了。
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set name xxxq
    QUEUED
    127.0.0.1:6379> get name
    QUEUED
    127.0.0.1:6379> set author zwq
    QUEUED
    127.0.0.1:6379> get author
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) "xxxq"
    3) OK
    4) "zwq"
    127.0.0.1:6379> get name
    "xxxq" 

watch命令:

  • watch命令是一个乐观锁。
  • 它可以在EXEC命令执行前,监视任意数量的数据库键,并在EXEC命令执行时,检查监视的键是否至少有一个已经被修改过了,如果修改过了,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。
  • 下面举一个例子,我开启两个Redis客户端,第一个客户端使用Watch命令监听name和author两个Key,随后使用multi命令开启事务,之后修改name的值。
    127.0.0.1:6379> watch name author
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set name xq
    QUEUED

     第二个客户端也执行修改name的值。

    127.0.0.1:6379> set name xxxxxq
    OK

     

  • 然后在第一个客户端执行EXEC命令,发现此次事务执行失败。因为在执行EXEC命令的时候,Watch命令监听到name已经被修改了,Redis认为此次事务已经不安全了,所以就无效化事务队列中的所有命令。最后,name的值为第二个客户端修改的结果。

11 Redis的发布与订阅

  1. Redis的发布订阅主要有三个角色:发布者订阅者频道(消息的载体)
  2. 频道是消息的载体发布者可以往特定频道发送消息订阅者可以订阅(也可以说是监听)特定频道的消息,一旦频道有了新的消息,就会推送给订阅者
  3. 最经典的应用场景就是微博和公众号,任何粉丝只要关注(订阅)了某一个人的微博或者公众号,该微博或者公众号只有有状态更新,都会将消息推送(发布)到粉丝…
  4. 命令:subscribe channel [channel..]:如subscribe “chat”表示订阅了"chat"频道。
  5. 这里还是打开两个客户端,第一个客户端(订阅者)订阅chat频道(当执行完上面命令的时候,第一个客户端处于监听状态),第二个客户端(发布者)往chat频道发送消息,然后第一个客户端就会立即收到消息。

12 Redis持久化机制(RDB+AOF)

为什么要将数据持久化?假如Redis服务器重启了一下,这时Redis之前缓存的数据都会消失,就得去MySQL等数据库查询数据,再备份到Redis中,这样会导致程序效率低下。而将Redis数据持久化之后,在重启Redis服务器的时候,只需要将持久化的数据拉取到内存中即可,这对于Redis每秒读写10次不算什么。Redis支持两种持久化方式:RDB(快照),AOF。

RDB:

  1. RDB(Redis Database)持久化是把当前进程数据生成快照保存在RDB文件的过程。
  2. RDB持久化生成的RDB文件是一个经过压缩的二进制文件。
  3. 触发RDB持久化过程分为手动触发和自动触发。

手动触发:

save命令和bgsave命令都可以实现手动触发。

save命令:
  1. 在客户端中执行save命令,就会触发Redis的持久化,但同时也是使Redis处于阻塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令,所以在生产环境一定要慎用。

bgsave命令:
  1. 执行bgsave命令,Redis父进程判断当前是否有正在执行的子进程,如RDB/AOF子进程,如果存在直接返回。
  2. 如果没有,父进程执行fork操作创建子进程,fork操作会阻塞父进程。父进程fork操作执行完成后,便不再阻塞父进程,可以继续响应其他命令。
  3. 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。
  4. 子进程发送信号给父进程表示完成,父进程更新统计信息。

     

自动触发:

  1. 自动触发的意思是Redis会判断是否满足持久化的条件,满足则自动执行bgsave命令进行持久化。是否满足则是通过Redis核心配置文件redis.conf来配置的。
  2. 接下来介绍有关RDB配置的内容:
    # RDB 保存的条件
    save 900 1
    save 300 10
    save 60 10000
    
    # bgsave 失败之后,是否停止持久化数据到磁盘,yes 表示停止持久化,no 表示忽略错误继续写文件。
    stop-writes-on-bgsave-error yes
    
    # RDB 文件压缩
    rdbcompression yes
    
    # 写入文件和读取文件时是否开启 RDB 文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。
    rdbchecksum yes
    
    # RDB 文件名
    dbfilename dump.rdb
    
    # RDB 文件目录
    dir ./
  3. save 参数:它是用来配置触发RDB持久化条件的参数,满足保存条件时将会把数据持久化到硬盘,使用的是bgsave命令。
    save m n:是指在 m 秒内,如果有 n 个键发生改变,则自动触发持久化——bgsave命令。
    save 900 1:表示 900 秒内如果至少有 1 个 key 值变化,则把数据持久化到硬盘。
    save 300 10:表示 300 秒内如果至少有 10 个 key 值变化,则把数据持久化到硬盘。
    save 60 10000:表示 60 秒内如果至少有 10000 个 key 值变化,则把数据持久化到硬盘。

  4. rdbcompression 参数:
    1. 它的默认值是yes表示开启RDB文件压缩,Redis会采用LZF算法进行压缩。如果不想消耗CPU性能来进行文件压缩的话,可以设置为关闭此功能,这样的缺点是需要更多的磁盘空间来保存文件。
  5. rdbchecksum 参数:
    1. 它的默认值为yes表示写入文件和读取文件时是否开启RDB文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。

RDB文件的载入:

  1. 如果Redis开启了AOF持久化功能,就会优先使用AOF文件来还原数据库状态,因为AOF文件的更新频率比RDB文件的更新频率高。
  2. 只有在AOF持久化关闭的状态时,服务器才会使用RDB文件来还原数据库状态。
  3. 服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

RDB的优缺点:

  1. 优点:RDB文件时一个紧凑的二进制文件,适用备份、全量复制的场景。加载RDB文件恢复数据远远快于AOF文件。
  2. 缺点:不能实时持久化,数据丢失的风险高。每次BGSAVE创建子进程属于重量级操作,频繁执行成本高。

AOF:

  1. AOF(Append Only File)持久化以独立日志方式记录每次写命令,重启时在重新执行AOF文件中的命令达到恢复数据的目的。
  2. AOF的主要作用就是解决了数据持久化的实时性。
  3. 被写入AOF文件的所有命令都是以Redis的命令请求协议格式保存的,因为Redis命令请求协议格式是纯文本格式,所以我们可以直接打开一个AOF文件观察里面的内容。
  4. 默认情况下AOF功能是关闭的。我们需要修改配置文件:appendonly no改为appendonly yes。

AOF工作流程:

  • 命令追加:服务器执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区末尾。
  • 文件同步:AOF缓冲区根据对应的策略向硬盘做同步操作。
  • 文件重写:随着AOF越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
  • 重启加载:当Redis重启时,可以加载AOF文件进行数据恢复。

文件同步策略:

  1. appendfsync always:每次写入都要同步AOF文件,从效率上看,是最慢的,从安全性上看,是最安全的。
  2. appendfsync everysec:每1秒钟同步一次,该策略为AOF的缺省策略。
  3. appendfsync no:从不同步。高效但是数据不会被持久化。

Redis明明是单线程,为什么性能还那么高?(面试题)

  1. 纯内存访问,Redis将所有数据放在内存中,内存响应的速度非常快。
  2. 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现的,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多事件。
  3. 单线程避免了线程切换和竞态产生的消耗。

单线程优缺点:

  1. 单线程可以简化数据结构和算法的实现。
  2. 单线程可以避免竞态产生的消耗,锁和线程切换通常是性能杀手。
  3. 对于每个命令的执行时间有要求,如果某个命令执行过长,会造成其他命令阻塞,对于Redis这种高性能的服务来说是致命的。

13 Redis流水线(管道)

  1. 以前我们每向Redis服务器发送一个命令的时候,Redis服务器就会执行该命令,然后返回结果给客户端。每一次请求与响应都会消耗一些时间,由于Redis读写速度很快,所有一旦接收到客户端发来的命令,就会立即执行并返回结果。
  2. 如果在网络差的情况下,客户端发送命令给服务器需要一些时间,此时Redis服务器一直等待命令的到来,这个原因不是Redis的问题,而是网络差的问题,如果一次性需要发送上百条命令,都会出现上述原因,那么效率是非常差的,所有Redis提供了一个流水线技术,也叫管道,就是能在一次连接上,执行大量的命令,减少客户端与服务器的来回交互次数,大大提升效率。

     

  3. 使用Java Jedis模拟批量插入100W条数据时,不使用与使用流水线技术的差距。
    @Test
    public void pipeline(){
        Jedis jedis = new Jedis("localhost",6379);
        //获取流水线
        Pipeline pipelined = jedis.pipelined();
        //获取开始执行时间
        long startTime = System.currentTimeMillis();
        IntStream.range(0,1000000).forEach(it -> pipelined.set("batch"+it,it+""));
        List<Object> list = pipelined.syncAndReturnAll();
        //获取执行完毕时间
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }
    耗时:3551毫秒,3秒多
    @Test
    public void set(){
        Jedis jedis = new Jedis("localhost",6379);
        long startTime = System.currentTimeMillis();
        IntStream.range(0,1000000).forEach(it -> jedis.set("setBatch"+it,it+""));
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }
    耗时:71948毫秒,71秒多

     

流水线总结:

从上面两个程序来看。使用流水线与不使用流水线的差距不是一般的大。

  1. 在一般情况下,用户每执行一个Redis命令,客户端和服务器都需要进行一次通信:客户端向服务器发送命令请求,而服务器则会将执行命令所得的结果返回给客户端。
  2. 在大多数情况下,执行命令时的绝大部分时间都耗费在发送命令和接收命令回复的过程上,因此减少客户端和服务器之间的通信次数,可以有效提高程序的执行效率。3. 流水线功能可以将多条命令打包一起发送,并且在一次命令恢复中包含所有被执行命令的结果,使用这个功能可以有效提升程序在执行多条命令时的效率。

 

标签:127.0,6379,Redis,redis,命令,key
From: https://www.cnblogs.com/zhao-ke-ming/p/18357927

相关文章

  • actix_web操作redis
    tomlactix-web="4"redis={version="0.21.4",features=["r2d2"]}r2d2="0.8.9"r2d2_redis="0.14.0"uuid={version="0.8",features=["v4"]}with_r2d2.rsuseredis::Comm......
  • Redis运维常用命令(持续更新)
    查看连接clientlist身份认证authpassword获取慢查询##结果为查询ID、发生时间、运行时长和原命令默认10毫秒,默认只保留最后的128条。单线程的模型下,一个请求占掉10毫秒是件大事情,注意设置和显示的单位为微秒,注意这个时间是不包含网络延迟的。SLOWLOGGET10##获取慢......
  • 场景题:百万数据插入Redis有哪些实现方案?
    在面试的过程当中,偶尔会遇到一些场景题,虽然这些场景题归根到底还是技术问题,但他通常比常规的八股题要稍微难一些,因为他考验的是你对于技术的整体理解、应用,以及变通的能力。那么今天咱们就来看一道,在面试中国平安时遇到的一道场景题:将百万数据插入到Redis,有哪些实现方案?1.Redis......
  • 如何将GB28181/GB35114流媒体监控视频汇聚平台LiveGBS中自带的redis组件换成自己的Red
    @目录1、Redis服务2、如何切换REDIS?2.1、停止启动REDIS2.2、配置信令服务2.3、配置流媒体服务2.4、启动3、搭建GB28181视频直播平台1、Redis服务在LivGBS中Redis作为数据交换、数据订阅、数据发布的高速缓存服务。默认LiveCMS解压目录下会携带一个REDIS服务。如果已经有自己的r......
  • redis迁移 redis-dump和redisshake
    redis-dump从a服务器redis1号库迁移到b服务器redis的1号库(网络不互通)ruby版本有要求,我服务器报错2.7.8以上1.安装需要的依赖yuminstallrubyrubygemsruby-devel-y2.配置秘钥curl-sSLhttps://rvm.io/mpapis.asc|gpg2--import-curl-sSLhttps://rv......
  • Redis 厨神:用 StringRedisTemplate 轻松获取数据的秘笈
    前言在这个快节奏的时代,数据处理就像烹饪,既需要精准的配料,又需要高超的烹饪技巧。想象一下,你在厨房里忙得不可开交,却被突如其来的订单搞得手忙脚乱。今天,我们要揭开如何用StringRedisTemplate轻松获取数据的秘密,让你在SpringBoot3.x的世界里,摇身一变,成为Redis的厨房大......
  • redis - 其他问题
    redis集群如何做故障转移redissentinelsentinel对redis数据节点和其他sentinel节点做定时监控,客户端连sentinel节点以获取数据节点的信息。如果心跳超过一段时间没回复,sentinel认为该节点主观下线,只有主节点主观下线,且sentinel之间互相通气发现超过一定数量的sentinel都判定为......
  • Redis 的位图(Bitmap)设计签到系统
    在使用Redis的位图(Bitmap)实现签到系统时,可以通过字符串的位定位(bitposition)来记录用户的签到状态。这是一种高效的存储和检索方式,因为你可以在一个字符串中使用位来表示二进制状态,通常每一位(bit)代表一个用户或一天的状态。以下是如何实现签到系统的思路:设计数据结构:每个......
  • Redis学习:十大数据类型、RDB持久化
    Redis学习文章目录Redis学习1.Redis-10大数据类型2.Redis持久化1.Redis-10大数据类型数据类型是value的类型,key的类型都是String数据类型是value的类型,key的类型都是String数据类型是value的类型,key的类型都是String命令不区分大小写,但key是区分大小写的命......
  • Redis面试题
    1.Redis的简介高速内存数据库:Redis是一个存储在内存中的数据库,这意味着它的读写速度非常快,非常适合做缓存;多种数据结构:字符串、哈希散列、列表、集合;持久化选择:可以选择将数据定期存储到磁盘中,防止重启时数据丢失;事务支持:可以将一组操作看成一个整体一起执行,确保了操作的完整......