?redis概述
- redis介绍
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用C语言编写、可基于内存亦可持久化的Key-Value数据库。
Redis是一个开源的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。
端口:6379
默认16个数据库,下标从0开始。
Redis是单线程+io多路复用。每秒读11万次,写8万次
- 为什么使用redis?
从性能和并发两个角度考虑:
性能方面-----我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应
并发方面-----在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
- redis为什么这么快
- Redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快。
- 数据结构简单,对数据操作也简单。
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题。
硬盘数据库的工作模式:

内存数据库工作模式:

?redis安装、启动、关闭
(1)先到redis官网(redis.io)下载最新版的redis压缩包到本地
(2)把本地的压缩包上传到 Linux 的 opt / software 目录下
(3)下载安装C语言编译环境,gcc编译器:yum install gcc
(4)解压压缩包:tar -zxvf redis-6.2.1.tar.gz
(5)解压完成后进入redis目录,进行编译:make
,编译完成进行安装:make install
(6)安装在 /usr/local/redis 目录下,安装好后bin目录下有六个文件
文件 | 作用 |
---|---|
redis-benchmark | 性能测试工具 |
redis-check-aof | 修复有问题的AOF文件 |
redis-check-dump | 修复有问题的dump.rdb文件 |
redis-sentinel | Redis集群使用 |
redis-server | 服务器启动命令 |
redis-cli | 客户端操作入口 |
(7)把 /opt/redis目录下的redis.conf 复制到 etc 下:cp redis.conf /etc/redis.conf
(8)将 redis.conf 里的 daemonize no 改成 yes(改成后台启动)
(8)启动 redis:redis-server /etc/redis.conf
(9)启动完成通过ps查看进程:ps -ef | grep redis
(10)启动后通过客户端连接redis:redis-cli
(11)单实例关闭:redis-cli shutdown
(12)多实例关闭(指定一个端口):redis-cli -p 6379 shutdown
?数据类型
- 五大数据类型

类型 | 说明 | 用途 |
---|---|---|
String | redis中最基本的类型。 value可以包含任何数据,比如jpg图片和序列化的对象 |
一般做一些复杂的计数功能的缓存。 |
Hash | 键值对集合,适合于对象的存储 | 在做单点登录的时候,存储用户信息。 |
List | 双向链表。使用List的数据结构,列表用来存储多个有序的字符串 | 消息队列 |
Set | 堆放的是一堆不重复值的集合。 | 可以做全局去重的功能。 |
Zset | Zset多了一个权重参数score,集合中的元素能够按score进行排列。 | 可以做排行榜应用,取TOP N操作。 |
- 三种特殊数据类型
类型 | 说明 | 用途 |
---|---|---|
Geospatial(Geo) | 地理位置 | 朋友定位,附近的人,两地之间的距离 |
Hyperloglog | 基数统计 | 网站的访问人数 |
Bitmaps | 位存储,用0和1表示状态 | 用户签到,用户在线状态 |
基数统计的概念: 比如数据集 {1, 2, 3, 3, 5, 5,}, 那么这个数据集的基数集为 {1,2,3,5}, 基数(不重复元素)为4。也就是说是不重复元素的个数。
?redis的常用操作
- 通用操作
- 切换数据库:
select index
查看当前数据库key的数量:
dbsize
查看当前库中所有的key:
keys *
清空当前库:
flushdb
清空所有库:
flushall
判断某个key是否存在:
exists key
查看key的类型:
type key
删除指定的key:
del key
给指定的key设置过期时间(单位:秒):
expire key 10
查看还有多少秒过期(-1表示永不过期,-2表示已过期):
ttl key
==注意:==设置了过期时间的key过期后则不存在,未设置过期时间的key是永不过期
- String 操作(key-String)
设置key-value:
set stringkey value
通过key拿到value:
get stringkey
- 同时添加多个key-value:
mset stringkey1 value1 stringkey2 value2……
- 同时获取多个key的value:
mget stringkey1 stringkey2 stringkey3
- 获取指定的key的value范围的值:
getrange stringkey sindex eindex
- 替换指定key某个位置开始的值:
setrange stringkey index value
- 给指定的key的value末尾追加值(key不存在时新增):
append stringkey value
- 获得指定key对应value的长度:
strlen stringkey
- 当key不存在时添加key-value:
setnx stringkey value
- 给指定key中数字值增1/减1(只对数字值操作):
incr / decr stringkey
- 给指定的key中数字自定义步长增减:
incrby / decrby stringkey 步长
- List 操作(Key-List)
==注意:==列表操作命令都是 l 开头
将一个值或多个值插入列表的头部(多个值用空格分隔):
lpush listKey v1 [v2 ……]
将一个值或多个值插入列表的尾部:
rpush listKey v1 [v2 ……]
获取列表的全部值:
lrange listKey 0 -1
获取列表的指定索引的值:
lrange listKey sindex eindex
![]()
获取列表的指定索引的值:
lindex listKey index
查看列表的长度:
llen listKey
- 截取列表的指定索引的值:
ltrim listKey sindex eindex
- 将列表指定索引的值替换为另一个值:
lset listKey index value
- 在指定列表某value前后插入其他value:
linset listKey brfore|after value othervalue
- Set 操作(Key-Set)
添加一个或多个值到 set(多个值用空格分隔):
sadd setKey v1 [v2 ……]
查看 set 的全部值:
smembers setKey
查看某个值是否在set中:
sismember setKey value
获取 set 中值的个数:
scard setKey
移除 set 某个值:
srem setKey value
从 set 中随机抽选出 n 个值(n不写默认为一个):
srandmember setKey n
从 set 中随机移除 n 个值:
spop setKey n
- Hsah 操作(Key-Map)
添加一个或多个值到 Hash 中(多个kv用空格分隔):
hset hashKey k1 v1 [k1 v1 ……]
获取 Hash 中的某个key:
hget hashKey key
获取 Hash 中所有的kv:
hgetall hashKey
移除 Hash 中的一个或多个key:
hdel hashKey key
查看 Hash 里面有多少个kv:
hlen hashKey
- 判断Hash中某的key是否存在:
hexists hashKey key
- Zset 操作(key-SortSet)
添加一个或多个值到 zset(n表示排序):
zadd zsetKey n1 v1 [n2 v2 ……]
获取指定索引范围内 zset 值(0 -1表示所有值):
zrange zsetkey sindex eindex
- 给 zset 进行排序(+/- inf 正/负无穷):
zrangebyscore zsetKey -inf +inf
- 获取集合中元素个数:
zcard zsetKey
- Geospatial 操作
将指定的地理空间位置(纬度、经度、名称)添加到指定的key中:
geoadd key 纬度 经度 地点名
从key里返回指定位置元素的位置(经度和纬度):
geopos key 地点名称
返回两个地点之间的距离(如果一个不存在则返回空):
geodist key 地点1 地点2 单位
以给定的经纬度为中心, 找出某一半径内的元素:
georadius key 纬度 经度 数值范围 单位
找出给定的位置元素指定范围内的元素:
georadiusbymember key 地点名 数值范围 单位
- 删除指定key下指定目标的数据:
zrem key 地点
- Heyperloglog 操作
添加指定元素到 HyperLogLog 中:
pfadd key element [element ……]
返回给定 HyperLogLog 的基数估算值:
pfcount key
- Bitmaps 操作
添加一个值到 bitmaps:
setbit key offset value
从 bitmaps 中取值:
getbit key offset
- 统计为1的数量(没start和end时默认统计全部):
bitcount key [start end]
?redis的配置
本机redis配置文件 redis.conf 拷贝在 /etc 目录下
- 单位
开头定义了一些基本的度量单位,只支持bytes,不支持bit
- 网络
(1)默认情况下 bind = 127.0.0.1 只能接受本机的访问请求。
(2)protected-mode yes 表示只能本机访问,不能远程访问。
==注意:==生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉。并且将protected-mode改为no
(3)port = 6379 redis的默认端口号
(4)timeout = 0 设置超时时间(客户端维持多少秒会关闭),0为永不超时
(5)tcp-keepalive 300 对访问客户端的一种心跳检测,设置为0则不检测
- 通用
(1)daemonize = yes 是否后台启动
(2)loglevel notice 指定日志记录级别,有四个级别,默认为notice
(3)databases 16 设置数据库的数目,默认为16
(4)maxclients 10000 设置redis同时可以与多少个客户端进行连接
(5)maxmemory < bytes > redis配置的最大内存容量
?发布和订阅
- 发布和订阅概述
发布和订阅是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接收消息。
redis客户端可以订阅任意数量的频道
(1)客户端可以订阅频道
(2)当给这个频道发布消息后,消息就会发送给订阅的客户端
- 命令行实现
(1)打开一个客户端订阅一个频道channel1:subscribe channel1
(2)打开另一个客户端,给channel1发布消息“hello” :publish channel1 hello
返回的1是订阅者的数量
(3)打开第一个客户端可以看到第二个客户端发送的消息
?Jedis
Jedis是使用Java来操作redis的中间件。
其API提供了比较全面的Redis命令的支持
(1)创建maven项目,导入jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
(2)使用Jedis
//生成一个Jedis对象,这个对象负责和指定的redis实例进行通信
Jedis jedis = new Jedis("192.168.229.128",6379);
//Jedis执行set操作(reids所有指令都在这)
jedis.set("name","lidu");
//Jedis执行get操作
String val = jedis.get("name");
==注意:==初始化 Jedis 需要两个参数,redis实例的 ip 和端口,要想通过 ip 访问 redis 必须在 redis.conf 配置文件中修改默认内容:
- 将 bind = 127.0.0.1 注释掉
- 将 peotected-mode = yes 改为 peotected-mode = no
同时Linux防火墙要关闭
- 检查防火墙状态:
systemctl status firewalld
- 关闭防火墙:
systemctl stop firewalld
?redis事务
- 事务概述
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
**作用:**就是串联多个命令防止别的命令插队。
Redis事务和关系型数据库的事务不太一样,它不保证原子性,也没有隔离级别的概念。
- 事务的执行和错误处理
Multi Exec Discard
- 从输入Multi命令开始,输入的命令都会依次进入队列中,但不会执行,直到输入Exec后,redis会将之前队列中的命令依次执行。组队的过程中可以通过Discard来放弃组队
- 组队中某个命令出现了错误,将要执行的整个队列都会被取消
![]()
- 执行阶段某个命令出现了错误,只有报错的命令不会被执行,其他的命令还是会执行
![]()
?redis持久化
redis提供了两种不同的持久化方式:
RDB(Redis DataBase)
AOF(Append Of File)
RDB默认开启,AOF默认不开启
- RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是快照,它恢复时是将快照文件直接读到内存里。
备份是如何执行的?
Redis会单独创建一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个文件替换上次持久化好的文件。
优点:
整个过程中,主进程是不进行任何I0操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
缺点:
最后一次持久化后的数据可能丢失。
- AOF
以日志的形式来记录每个写操作(增量保存),将redis执行过的所有写指令记录下来,读操作不记录;只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件内容将写指令从前到后执行一次,以完成数据的恢复工作。
持久化流程
- 客户端的请求写命令会被append追加到AOF缓冲区内;
- AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
- AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
- Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的
![]()
?主从复制
- 主从复制概述
主从复制是主机数据更新后根据配置和策略,自动同步到备机的 master / slaver 机制。
master 以写为主,slaver 以读为主,一主多从
作用:
- 读写分离(主做写操作,从做读操作),性能扩展
- 容灾快速恢复
?redis集群
- 集群概述
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis集群通过分区来提供一定程度的可用性,即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
容量不够,redis如何进行扩容?
并发写操作,redis如何分摊?
之前通过代理主机来解决,redis3.0中提供了无中心化集群配置
代理主机模式:
![]()
无中心化集群:
![]()
?应用问题解决
- 缓存穿透
去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而导致数据库连接异常。(一般是遇到黑客攻击)
解决方案:
- 对空值缓存
如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果(null)进行缓存,设置很短的过期时间。
- 设置白名单
使用bitmaps类型定义一个白名单,里面放可以访问的id,每次访问的时候拿访问的id和bitmaps里面的id比较,如果访问的id不在bitmaps里面,进行拦截。
- 缓存击穿
redis某个key过期了,大量访问需要使用这个key,数据库访问压力瞬时增加
解决方案:
- 加长这些key的过期时间
- 监控那些热门数据,实时调整key的过期时长
- 缓存雪崩
即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,而导致数据库连接异常。
解决方案:
- 添加随机值
给缓存的失效时间加上一个随机值,避免集体失效。
- 构建多级缓存架构
Nginx缓存 + Redis缓存 + 其他缓存
?分布式锁
- 概述
随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
分布式锁应该具备的条件:
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
- 高可用的获取锁与释放锁
- 高性能的获取锁与释放锁
- 具备锁失效机制,防止死锁
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
实现方式:
- 基于数据库
- 基于redis(性能最高)
- 基于zookeeper(可靠性最高)
- 命令实现
nx:表示上锁
ex:表示设置过期时间(单位秒)
==注意:==在过期时间内,key还能使用,过期后key就消失了