Redis


Redis基础知识

  • Redis可用作数据库、缓存、和消息中间件
  • Redis默认有16个数据库
  • Redis启动路径在 /usr/bin/redis-cli
  • 操作后返回值为1为成功,0则失败
  • Redis数据类型:String,Set,Hash,Zset以及三种特殊数据类型:geospatial,hyperloglog,bitmaps;
  • Redis是单线程的,是基于内存操作,CPU不是redis性能瓶颈,redis性能瓶颈是根据机器的内存和网络带宽,既然可以用单线程来实现,所以就用单线程了!
  • Redis核心:redis是将所有的数据全部放到内存中,所以说单线程去操作效率就是最高的,多线程(CPU上下文会切换,耗时的操作),对内存来说,没有上下文的切换就是最高的。

Redis的基本使用

开启、关闭、退出:


  • 开启redis服务:service redis start
  • 关闭redis服务:shutdown(结束redis进程)
  • 退出redis:exit 或者 quit

基本操作:


  • 查看所有Key:**Keys ***
  • 选择数据库:select 数字编号(默认16个)
  • 查看数据库大小:dbsize
  • 清空全部库:flushall
  • 清空当前库:flushdb
  • 查看Key是否存在:Exists Key名
  • 过期时间:Expire key seconds(时间)
  • 查看过期时间:ttl key
  • 查看数据类型:type key
  • 移动Key:move key db
  • 删除key:del key

String:


  • 追加:append key 追加内容(如果key不存在,就相当于set key)
  • 获取字符串长度:strlen
  • 增量:incr key(对value加1)
  • 减量:decr key(对value减1)
  • 步长:decrby key 步长(自定义增量)
  • 截取:getRange key start end(end为-1时,查看全部字符串;[start end]闭区间的方式)
  • 替换:setRange key offset value
  • 批量set:mset key1 value1 key2 value2
  • 批量get:mget key1 key2
  • 对象:例 set user:1 {name:ikart,age:20}设置一个user:1对象,值为json字符串来保存一个对象;
  • 设置过期时间:setex(set with expire)
  • 不存在再设置:setnx(set if exist)(如果key不存在则设置,key存在则失败,分布式锁中常常使用)
  • 批量不存在再设置:msetnx (原子性操作)
  • getSet:先get然后set,如果不存在则返回nil后set,存在则获取原来的值后再set

List列表:


所有list命令都是以L开头的

>>插入

  • lpush:将一个或多个元素,插入到列表的头部
  • rpush:将一个或多个元素,插入到列表的尾部
  • insert:将指定的一个元素,插入到列表另一个元素的前面或后面(before,after )

>>移除

  • lpop:移除列表头部的一个元素
  • rpop:移除列表尾部的一个元素
  • lrem:移除指定个数的元素,精确匹配

>>截取、替换

  • trim

  • rpoplpush:移除列表最后一个元素到指定列表(集合)中;

  • lset:将列表中指定下标元素替换为另一个元素,更新操作

  • lindex:获取某一个list列表中索引元素

  • llen:查看list长度

Set集合:


无序不重复集合;set的命令以s开头

  • sAdd:向set集合中添加元素
  • sMembers:查看指定set集合中所有成员;(member:成员)
  • sIsMember:判断某一个元素是否在指定的set集合中;
  • scard:获取set集合中元素的个数;
  • srem:移除指定元素
  • sRandMember:随机抽取集合中指定个数的元素
  • sPop:随机删除一个元素
  • sMove:将一个指定集合中的元素移动到另一个set集合中

>>交、并、差集

  • sDiff:差集
  • sInter:交集(应用:共同好友)
  • sUnion:并集

Hash集合:


Map集合类型 key-value;hash命令以h开头;适用于对象的存储,信息经常变动的场景(对象用hash!!!);

  • hSet:set一个具体的key-value;指定增量
  • hmSet:set多个key-value
  • hGet:获取一个字段值
  • hmGet:获取多个字段值
  • hGetAll:获取指定hash集合中全部数据
  • hLen:获取hash表长度
  • **hExists:**判断hash中指定字段是否存在
  • hkeys:获取hash表中所有的key
  • hvals:获取hash表中所有的value
  • hincrby:增量、自定义步长;
  • hsetnx:如果不存在则可以设置,存在则不可设置
  • hDel:删除hash集合中一组键值对

Zset(有序集合):


有序的集合,在set基础上,增加了一个属性score。

  • zadd:添加一个或多个元素
  • zRangeByScore:排序,从小到大
  • zRevRange:排序,从大到小
  • zRem:移除指定元素
  • zCrad:计算有序元素个数

***** -inf、+inf表示无穷小和无穷**

三种特殊数据类型

geospatial(地理位置):


有效的经度范围 -180度~180度;
有效的纬度范围 -85.05度~85.05度;

Geo底层实现是基于Zset,所以可以使用Zest来操作Geo;

  • GeoAdd:添加地理位置 格式:geoadd key 经度 纬度 城市名
  • **GeoDist:**获取两点间的距离 geodist key 城市1 城市2 单位 (M:米 Km:千米 FT: 英尺 MI:英米)
  • **GeoPos:**获取指定指定城市的经纬度 格式:geopos key 城市名
  • **GeoRadius:**已给定的经纬度为中心,找出某一半径内的元素
  • **GeoRadiusByMember:**以指定城市为中心,找出某一半径内的元素

hyperloglog(数据结构):


基数:不可有重复的数;
0.81%的误差率

PFadd:创建一组元素

PFcount:统计一组元素中的数量

PFmerge:以并集的方式将两组合并为一个新的组

bitmap(位存储):


统计用户信息,活跃还是不活跃,只有0和1两个状态

setBit:设置

getBit:获取

BitCount: 统计

事务


1、Redis的原子性:单条命令式保证原子性,但是事务不保证原子性;
2、Redis事务本质:一组命令的集合;一个事物中所有命令都会被序列化,在事务的执行过程中,会按照顺序执行,一致性,顺序性,排他性,执行一些列的命令。
3、Redis事务没有隔离级别的概念

Redis的事务

正常执行事务:

  1. 开启事务:multi
  2. 命令入队
  3. 执行事务:exec

放弃执行事务:

  • DisCard:放弃执行事务

异常:

  • 编译型异常:事务中所有命令都不会被执行。
  • 运行时异常:存在语法性错误时,其他命令正常执行,错误命令抛出异常。

监控(Watch)


悲观锁:

  • 很悲观,认为程序随时都会出问题,无论做什么都会加上锁

乐观锁:

Watch是乐观锁

  • 认为程序不会出问题,所以没有上锁,更新数据的时候去判断一下是否有他人修改过数据
  • 获取version
  • 更新的时候比较version

>>当乐观锁的数据发生了变动的处理:

  1. 如果发现事务执行失败,就先解锁(unWatch)
  2. 获取最新的值,再次监视(Watch)
  3. 对比监视的值是否发生了变化,如果没有变化,那么可以执行成功,如果变量就执行失败。

Redis%206b54717c8023468f937432876014d332/Untitled.png

Jedis

Jedis是Redis官方推荐的Java连接开发工具,使用Java来操作Redis中间件。

第一步:导入依赖

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>

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

第二步:编码测试

  1. 连接数据库
  2. 操作命令
  3. 断开连接

SpringBoot整合:

原来使用的Jedis被替换了Lettuce

Jedis:采用直连的方式,多个线程操作的话是不安全的,如果想要避免不安全,使用Jedis pool连接池,更像BIO(阻塞)。

Lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据,更像NIO(非阻塞)模式。

Redis%206b54717c8023468f937432876014d332/Untitled%201.png

Redis.conf解析:


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

网络:

bind 127.0.0.1 #绑定的ip
port 6379 #redis的端口

通用(GENERAL):

loglevel notice   #日志类型,有**debug,verbose,**notice ,warning
logfile ""     #日志文件位置名
databases 16   #默认数据库的数量
always-show-logo yes  #是否显示Redis启动图形
logfile "/www/server/redis/redis.log"  #日志信息地质

快照:

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

#如果900秒内,如果至少有1个Key进行了修改,则进行持久化操作
save 900 1
#如果300秒内,如果至少有10个Key进行了修改,则进行持久化操作
save 300 10
#如果60秒内,如果至少有10000个Key进行了修改,则进行持久化操作
save 60 10000

stop-writes-on-bgsave-error yes  #如果持久化出错,是否还要继续工作。
rdbcompression yes  #是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes #保存rdb文件的时候,进行错误的校验
dir /www/server/redis/  #rdb文件保存的目录
dbfilename dump.rdb #rdb文件名

主从复制(REPLICATION):


安全(SECURITY):

Redis默认没有密码

requirepass foobared #设置redis的密码

#获取密码,默认为空
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) ""

#通过连接设置密码
127.0.0.1:6379> CONFIG set requirepass "password"
OK

#登录 auth [用户名] 密码
127.0.0.1:6379> auth password
OK

Redis持久化


1、将内存中的数据写入磁盘,Redis默认采用RDB的方式。
2、RDB:指定间隔时间内将内存中的数据写入磁盘。
3、AOF是将每一步命令记录下来(只记录写的操作),恢复的时候就把这个文 件全部在执行一遍。

RDB(Redis DataBase):

RDB:指定间隔时间内将内存中的数据写入磁盘。

rdb保存的文件时dump.rdb

Redis%206b54717c8023468f937432876014d332/Untitled%202.png

触发机制:

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

如何恢复rdb中的数据?

Redis自动配置好了,了解即可!

  1. 将rdb文件放在Redis启动目录就可以,Redis启动的时候会在自动检查dump.rdb恢复其中的数据
  2. 查看需要存在的位置
#Redis的启动目录
127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/www/server/redis"

RDB的优点:

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

RDB的缺点:

  • 需要一定时间间隔进程操作,如果Redis宕机了,则可能造成一些数据的丢失。
  • fork进程的时候,会占用一定的内存空间。

AOF(Append Only File):

AOF是将每一步命令记录下来(只记录写的操作),恢复的时候就把这个文件全部在执行一遍。

AOF保存的是appendonly.aof文件。

默认是不开启的;开启在配置文件中将appendonly 设置为yes即可!

AOP默认为无限追加,文件会越来越大,所以超过64mb后会fork一个新的线程将文件进行重写(可以指定文件大小后重写)

如果两种配置都开启了,则优先AOF

Redis%206b54717c8023468f937432876014d332/Untitled%203.png

注意项:

  1. 如果aof文件有错位,此时Redis是不能启动的,使用redis-check-aof来修复(在Redis的src目录下)
#修复:./redis-check-aof --fix 要修复的文件路径
[root@iZf0railv34ewtZ src]# ./redis-check-aof --fix ../appendonly.aof
0x              a7: Expected \r\n, got: 6673
AOF analyzed: size=183, ok_up_to=142, diff=41
This will shrink the AOF from 183 bytes, with 41 bytes, to 142 bytes
Continue? [y/N]: y
Successfully truncated AOF

#修复完成后重启redis服务
[root@iZf0railv34ewtZ src]# service redis start 
Starting redis server...
Starting redis success!
#再次连接即可!
[root@iZf0railv34ewtZ src]# redis-cli 
127.0.0.1:6379>

AOF的配置:

#追加的配置,默认每秒钟同步一次
# appendfsync always
appendfsync everysec
# appendfsync no

#AOP默认为无限追加,文件会越来越大,所以超过64mb后会fork一个新的线程将文件进行重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

AOF的优点:

  • 每一次同步修改,文件的完整性会更好
  • 每秒钟同步一次,可能会丢失一秒钟的数据

AOF的缺点:

  • 相对于数据文件来说,AOF远远大RDB,修复速度也比RDB
  • AOF运行效率也比RDB慢

发布订阅


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

命令

  • PSubscribe:订阅一个或多个符合给定模式的频道
  • Pubsub:查看订阅与发布系统状态
  • Publish:将信息发送到指定的频道 **
  • PunSubscribe:退订所有给定模式的频道
  • Subscribe:订阅给定的一个或多个频道信息
  • UnSubscribe:退订指定的频道

Redis%206b54717c8023468f937432876014d332/Untitled%204.png

Redis主从复制


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

主从复制,读写分离;主机负责写,从机负责读。

主从复制的作用:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;

  • 故障恢复:当节点出问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务冗余;

  • 负载均衡:当从机复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担负载,可以大大提高Redis服务器的并发数量。

  • 高可用(集群)基石:除了上述作用外,主从复制还是哨兵和集群能够实施,因此说主从复制是Redis高可用的基础;

    复制原理:

    全量复制:slave服务在接收到数据库文件后,将master所有数据存盘并加载到内存中。

    增量复制:Master继续将所收集到的修改命令依次传给slave,完成同步。

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

Redis%206b54717c8023468f937432876014d332/Untitled%205.png

主从配置:手动模式

如果只有一台主机时只能模拟,复制多份redis.config(不同名),修改里面的端口号 ,日志文件名,rdb文件名及pid文件名即可 。
启动指定的redis:./redis-server ../temp/redis6381.conf

只需要配置从库,主库不用配置。因为redis默认是主机

方式一:在Redis-cli里面配置(暂时的)

shutdown后重启自动变回独立的主机,不能够读取到shutdown之后的数据;再次变为从机数据就能立马回来

恢复主机身份 :slaveof no one 即与主机断开连接

#从机配置
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #添加主机信息
OK

#主机信息
127.0.0.1:6379> info replication     #查看redis配置信息
# Replication
role:master   #redis的角色
connected_slaves:2    #从机数量
slave0:ip=127.0.0.1,port=6380,state=online,offset=210,lag=0 #第一台从机信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=210,lag=1 #第二台从机信息
master_failover_state:no-failover
master_replid:332f9d1a52c489d0a31a1d239544cf8ddc2ce805
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210

方式二:修改配置文件(永久的,服务启动即生效)


主从配置:哨兵模式(自动选举主节点):

第一步:编写配置文件sentinel.conf

sentinel monitor 主机名(随意) ip地址 port 1

第二步:运行redis-sentinel

./redis-sentinel ../temp/sentinel.conf

Redis缓存穿透和雪崩:


缓存穿透:

缓存穿透:简单来说就是用户查询一个数据,发现Redis里面没有,也就是缓存没有命中。于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库,给持久层数据库造成很大的压力,这时候就相当出现了缓存穿透。

缓存穿透的解决方案

方式一:布隆过滤器:

 *布隆过滤器:是一种数据结构,对所有查询的参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力*

Redis%206b54717c8023468f937432876014d332/Untitled%206.png

方式二:缓存空对象

   当存储层不命中后,即使返回空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据会从缓存中获取,保护了后端数据源。 

Redis%206b54717c8023468f937432876014d332/Untitled%207.png

缓存击穿:

缓存击穿:即某一个key非常热点,在不停的抗着大的并发,大并发集中对这一点进行一个访问,当这个key在失效的瞬间(缓存过期,访问数据库来查询最新数据的时间间隙),持续的大并发就穿破缓存,直接请求数据库,就像在墙上凿开了一个洞。

解决方案

  • 设置热点数据永不过期:从缓存层面来看,没有设置过期时间,所以就不会出现key过期后产生的问题。
  • 加锁斥锁;分布式锁:保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转到了分布式锁,所以对分布式锁的考验很大。

缓存雪崩

缓存雪崩:在某一段时间内,缓存集中过期生效,Redis宕机。更致命的是Redis服务器因为不可抗拒的因素造成缓存雪崩,如断电、断网

  • Redis高可用:集群,设置多台Redis;
  • 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库缓存的数量。例如对某个key只允许一个线程查询数据和缓存,其他在线程等待;
  • 数据预热:数据预热就是在正式部署之前,把数据先预先访问一遍,这样部分可能大量访问的数据会加载到缓存中,在即将发生并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

参考文献

Redis 教程

redis中文官方网站

代码示例

Q.E.D.


在等花开,等春天来.