笔记记录 B站狂神说Java的Redis课程:https://www.bilibili.com/video/BV1S54y1R7SB
现在是大数据时代(一般的数据库已经解决不了的数据:大数据(海量存储和并行计算))
SQL => NoSQL
1、单机时代(如MySQL)的瓶颈
2、Memcached缓存(减轻服务器的压力) + MySQL + 垂直拆分(读写分离,多个MySQL服务器,有的值负责读,有的值负责写)
发展过程:①优化数据结构和索引 => ②文件缓存(涉及IO操作)=> ③Memcached
3、分库分表 + 水平拆分 + MySQL集群
Not Only SQL —> 非关系型数据库
NoSQL特点
大数据时代的3V:主要是描述问题的
大数据时代的3高:主要是对程序的要求
电商网站:
# 1、商品的基本信息
名称、价格、商家信息:
关系型数据库: MySQL / Oracle (王坚:阿里去IOE(IBM小型机,Oracle数据库、EMC存储器))
# 2、商品的描述、评论(文字多)
文档型数据库:MongoDB
# 3、图片
分布式文件系统:FastDFS、TFS(淘宝)、GFS(Google)、HDFS(Hadoop)、OSS云存储(阿里云)
# 4、商品的关键字(搜索)
搜索引擎:solr、ElasticSearch、ISearch(阿里:多隆)
# 5、商品热门的波段信息
内存数据库:Redis、Tair、Memcached、...
# 6、商品的交易、外部的接口
三方应用
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo Cabine/Tyrant Redis Voldemort Oracle BDB |
内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等 | Key指向Value的键值对,通常用hashtable来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra HBase Riak |
分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB MongoDB |
Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严谨,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法 |
图形数据库 | Neo4J InfoGrid Infinite Graph |
社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案 |
官网:redis.io
中文网:http://www.redis.cn/
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
linux 使用的系统是 Centos 7.3
你可使用 虚拟机,也可已使用云服务器(学生机不贵),因为我有服务器,这里就在服务器上安装和学习Redis
①下载安装包
http://www.redis.cn/
②加压安装包(提前使用xshell和xftp上传压缩包到服务器上)
压缩包在 /app/目录下
在/usr/local/ 下创建redis文件夹,并进入到文件夹中
解压压缩包到该文件夹下
tar -zxvf /app/redis-6.0.6.tar.gz -C ./
③编译并安装(保证安装了编译环境)
没有安装编译环境,需要 yum install gcc-c++
如果下面操作报错:需要提升GCC的版本(redis6需要5.3+)
//升级gcc到9以上 yum -y install centos-release-scl yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils //临时将此时的gcc版本改为9 scl enable devtoolset-9 bash //或永久改变 echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
cd redis-6.0.6make && make install
编译后,默认安装路径在 /usr/local/bin
④安装系统服务并后台启动
可能会报错:
解决方法:vim install_server.sh,注释如下图内容
安装系统服务(可以指定选项,下面默认)
cd utils./install_server.sh
默认的配置文件:/etc/redis/6379.conf(安装成功后,默认后台自启)
⑤开启redis服务
可以使用自定义的配置文件开启,这里使用systemctl
/redis安装目录/redis-server /配置文件目录/redis.conf# 关闭服务/redis安装目录/redis-cli shutdown
systemctl start redis_6379.service
⑥客户端连接测试
⑦设置远程连接
vim /etc/redis/6379.conf
然后重启服务即可:
systemctl restart redis_6379.service
⑧配置访问密码
vim /etc/redis/6379.conf
重启服务
systemctl restart redis_6379.service
客户端连接测试
关闭连接
使用自带的
redis-benchmark
工具测试即可
redis-benchmark [option] [option value]
性能测试工具可选参数:
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 3 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 |
1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | —csv | 以 CSV 格式输出 | |
12 | -l | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
# 测试:100个并发连接 100000请求redis-benchmark -c 100 -n 100000
如果觉得这里命令不太好看,去这个博客https://www.cnblogs.com/wlandwl/p/redis.html,或者 官网:http://www.redis.cn/commands.html
注意:以下的所有key都表示对应数据类型的 键的名称
; value表示存储的值
(除非注释中有特殊说明)
可以使用
select
进行切换
使用
dbsize
查看数据库大小
使用
keys *
flushdb
flushall
略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AzzXg8Ea-1642752175414)(https://www.kuangstudy.com/bbs/2020-11-2-Redis%E8%AF%A6%E7%BB%86%E7%AC%94%E8%AE%B0.assets/image-20201112020411584.png)]
exists
key
move
key 1 // 1表示当前数据库
del
key // 删除当前数据库的key(可以多个)
expire
key 时间 // 单位时间为s
ttl
key // 查看剩余存活时间
type
key
append
key appendValue
strlen
key
incr
key
decr
key
incrby
key 步长
decrby
key 步长
getrange
key startIndex endIndex
setrange
key index replaceString
setex
key 时间 value // 设置值,带过期时间
setnx
key value // 如果不存在,则设置
mset
k1 v1 k2 v2 …
mget
k1 k2 …
msetnx
批量设置
getset
key value // 先get再set(如果不存在,先返回nil,在设置值;如果存在,就先返回原值,再设置新值)
rename
key newName
randomKey
save
阻塞
bgsave
非阻塞
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
阻塞? | 是 | 是(阻塞发生在fock(),通常非常快) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外的内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fock子进程,消耗内存 |
config get requirepass
config set requirepass password
auth password
设置密码后必须认证才能使用客户端功能
shutdown
info
参数
参数列表:
server
: Redis服务器的一般信息clients
: 客户端的连接部分memory
: 内存消耗相关信息persistence
: RDB和AOF相关信息stats
: 一般统计replication
: 主/从复制信息cpu
: 统计CPU的消耗commandstats
: Redis命令统计cluster
: Redis集群信息keyspace
: 数据库的相关统计它也可以采取以下值:
all
: 返回所有信息default
: 值返回默认设置的信息如果没有使用任何参数时,默认为default
。
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
单点登录、
略,具体看前面
lpush
key value
rpush
key value
lrange
key 0 -1
lpop
key
rpop
key
lindex
key index
llen
key
lrem
key count(移除的个数) element
ltrim
key startIndex endIndex
lset
key index value // 将列表中 指定index的值替换为对应的value
linsert
key before|after pivot(那个单词后) value
rpoplpush
source destination(newList) // 先移除source最后的元素,再将该元素添加到newList中
集合中的值不能重复(无序)
sadd
key member
smembers
key
sismember
key member
scard
key
srem
key member
srandmember
key [count]
spop
key [count]
smove
source destination member(需要移动的成员)
sdiff
key1 ,key2 …sinter
key1,key2 …sunion
key1 ,key2 …key-Map or key-
,value是一个Map Hash本质和hash没有区别,只是value变成了Map
用户信息保存,经常变动的信息,适合对象的存储
hset
key field value [k1 v1 [k2 v2 …]]
hget
key field
hgetall
key
hdel
key field
hexists
key field
hkeys
key // 此处的key是Map名
hvals
key
hincrby
key field value
hsetnx
key field value // field 在map中不存在就添加这个值,否则不做改变
在Set基础上增加了一个值(用于排序的值)
存储班级成绩表,工资表排序,
普通消息 = 1,重要消息 = 2,带权重进行判断
排行榜应用实现
zadd
key n value
zrange
key startIndex endIndex
zrangebyscore
key -inf +inf [withscores] // 升序
zrange
key 0 -1
zrevrangebyscore
key +inf -inf [withscores] // 降序
zrevrange
key 0 -1
zrangebyscore
key -inf 任意值n // 升序 + 显示区间 [-inf,n]
zrem
key value
zcard
key
zcount
key startIndex endIndex
朋友的定位,附近的人,打车距离计算
底层是
Zset
,即可以使用Zset的命令操作Geospatial
Redis3.2就支持了
geoadd
key 经度 纬度 名称
geopos
key 名称
geodist
key
georadius
key 经度 纬度 半径 单位
georadiusbymember
key 成员名 半径 单位
geohash
key 成员1 成员2
基数统计的算法
基数:集合中元素的个数(先去重),如{1,2,2,3} 其基数为3(集合去重后为1,2,3 有3个元素)
网页的UV(一个人访问访问一个网站多次,但是还是算作一个人)
传统实现UV:Set保存用户的Id,然后统计set中的元素的数量作为标准判断(这种需要保存大量用户的ID)
Redis2.8.9
创建一组元素 :
pfadd
key ele1 ele2 ele3 …统计对应key的基数:
pfcount
key1 [key2 …] // 多个key 就是统计这些key并集的基数合并:
pfmerge
destkey sourceKey1 sourceKey2 [sourceKey3 …]
位存储,位图(操作二进制)
统计用户信息,活跃,不活跃!登录、未登录!打卡,365打卡!(两个状态都可以使用)
一周过去
setbit
key offset bit
查看单天打卡情况
getbit
key offset
统计所有打卡的天数
bitcount
key
注意:Redis单条命令是保证原子性的;但是事务不保证原子性!
Redis事务没有隔离级别的概念,所有的命令在事务中,并没有直接被执行,只有发起执行命令时才执行
**Redis事务本质:**一组命令的集合,一个事务中的所有命令都会别序列化,在事务执行过程中,会按照顺序执行!
--- 队列 set set set 执行 ---
一次性、顺序性、排他性!执行一些列的命令
Redis的事务:
multi
)其他命令
)exec
)正常事务的执行
取消事务
discard
异常执行
整个命令队列都不会执行
报错语句,会抛出异常;其他语句照样运行
演示‘
①开启俩个,客户端,模拟多线程情况
②左边支出20元(但是不执行事务),然后右边修改money的数值
③左边执行事务,发现执行操作返回nil,查看money和out,发现事务并没有被执行(确实有乐观锁的效果)
如果修改失败获取最新的值就好(exec
、unwatch
、``discard`都可以清除连接时所有的监视)
Jedis是一个用java写的Redis数据库操作的客户端,通过Jedis,可以很方便的对redis数据库进行操作
①新建空maven项目
②导入依赖
redis.clients jedis 3.3.0 com.alibaba fastjson 1.2.70
③编码
下面使用远程连接需要的操作:https://blog.csdn.net/weixin_43423864/article/details/109087670
public class PingTest { public static void main(String[] args) { // 1. new Jedis 对象 Jedis jedis = new Jedis("ip地址",6379); // 如果设置密码 需要认证,没有设置忽略下面这条语句 jedis.auth("密码"); /// jedis 所有的命令(方法)都是之前学的命令 System.out.println(jedis.ping());// 测试连接 }}
操作
// 连通Jedis jedis = new Jedis("ip地址", 6379);jedis.auth("密码");//基本操作System.out.println("清空数据:" + jedis.flushAll());System.out.println("判断key(name)是否存在:" + jedis.exists("name"));System.out.println("设置name的value:" + jedis.setnx("name", "liuyou"));System.out.println("设置pwd的value:" + jedis.setnx("pwd", "密码"));System.out.println("打印所有的key:" + jedis.keys("*"));System.out.println("获取该name的value:" + jedis.get("name"));System.out.println("删除pwd:" + jedis.del("pwd"));System.out.println("重命名name为username:" + jedis.rename("name", "username"));System.out.println("打印所有的key:" + jedis.keys("*"));System.out.println("返回当前数据库中key的数目:" + jedis.dbSize());
清空数据:OK判断key(name)是否存在:false设置name的value:1设置pwd的value:1打印所有的key:[pwd, name]获取该name的value:liuyou删除pwd:1重命名name为username:OK打印所有的key:[username]返回当前数据库中key的数目:1
关闭连接
jedis.close()
// 连通Jedis jedis = new Jedis("IP地址", 6379);jedis.auth("密码");JSONObject jsonObject = new JSONObject();jsonObject.put("hello","world");jsonObject.put("name","liuyou");jsonObject.put("pwd","密码");String s = jsonObject.toJSONString();jedis.flushAll();/// 加监听 watch// jedis.watch("user");// 开启事务Transaction multi = jedis.multi();try { multi.set("user",s); // 其他语句 // 执行事务 multi.exec();} catch (Exception e) { // 取消事务 multi.discard(); e.printStackTrace();} finally { System.out.println(jedis.get("user")); // 关闭连接 jedis.close();}
在SpringBoot2.x之后,原来的jedis被替换为lettuce
RedisAutoConfiguration
源码分析1、导入依赖
org.springframework.boot spring-boot-starter-data-redis
2、配置Redis(application.xml)
# SpringBoot 整合Redisspring.redis.host=xxxspring.redis.port=6379spring.redis.password=liuyou
3、编写测试类
@SpringBootTestclass Redis02SpringbootApplicationTests { @Autowired RedisTemplate redisTemplate; @Test void contextLoads() { //redisTemplate // 1.使用redisTemplate.opsForxxx 操作对应的数据结构 // 2.可使用redisTemplate 进行简单的key操作,如multi、move、watch、keys 等操作 /// 3.可使用获取连接,通过连接进行更多操作 // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); // RedisZSetCommands redisZSetCommands = connection.zSetCommands(); // 这里只使用1.演示 ValueOperations str = redisTemplate.opsForValue(); str.set("name","liuminkai刘民锴"); System.out.println(str.get("name")); }}
但是存在问题,没有序列化,存储在Redis中的中文会被转义,如下图
为啥会出现这个情况? RedisTemplate默认序列化使用JDK的,我们需要使用JSON格式
如何解决?我们需要编写自定义Redis配置类,自定义RedisTemplate
@Configurationpublic class MyRedisConfig { // 更改Key :Object ==> String 符合日常使用 // 自己定义了一个 RedisTemplate @Bean @SuppressWarnings("all") public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 我们为了自己开发方便,一般直接使用 RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); // Json序列化配置 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // String 的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; }}
自定义RedisTemplate后,再启动③的测试类,结果Redis中正常显示
日常开发我们都不会使用原始的
RedisTemplate
,都会封装一个RedisUtils工具类便于使用
package com.liuyou.utils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.Map;import java.util.Set;import java.util.List;import java.util.concurrent.TimeUnit;@Componentpublic final class RedisUtils { @Autowired private RedisTemplate redisTemplate; // =============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * @param key 键 * @param delta 要增加几(大于0) */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * @param key 键 不能为null * @param item 项 不能为null */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map
使用该工具类直接
@Autowired
注入即可
学会前面的所有内容 ==> Redis基础
高级操作都在后面
我们启动Redis,一般都是通过Redis.conf启动(我前面是使用安装服务的6379.conf启动)
因此,我们必须了解Redis.conf的配置,才能更好理解和使用Redis
单位
包含
网络
bind 0.0.0.0 # IP(默认127.0.0.1)protected-mode no # 保护模式(默认yes)port 6379 # 端口设置(默认6379)
通用
daemonize yes # 以守护进程方式运行,即后台运行(默认no)pidfile /var/run/redis_6379.pid # 如果以后台运行,必须指定一个pid文件# 日志# Specify the server verbosity level.# This can be one of:# debug (大量信息, 使用于测试或开发阶段)# verbose (许多很少有用的信息,但不像调试级别那样混乱)# notice (比较冗长,你可能想在生产环境中使用)# warning (只有非常重要/关键的消息被记录下来)loglevel notice # 默认noticelogfile "" # 日志的文件位置名databases 16 # 数据库的数量(默认16)always-show-logo yes # 是否开启 logo (默认yes)
快照
持久化,在规定时间内,执行了多少次操作,会被持久化到文件(.rdb,.aof)
save 900 1 # 900秒内(15分钟),如果至少有1个Key进行修改,我们就进行持久化操作save 300 10 # 300秒内(5分钟),如果至少有10个Key进行修改,我们就进行持久化操作save 60 10000 # 60秒内(1分钟),如果至少有10000个Key进行修改,我们就进行持久化操作stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作(默认yes)rdbcompression yes # 是否压缩rdb文件(默认yes),会消耗一些CPU资源rdbchecksum yes # 保存rdb文件时,进行错误检查检验dir ./ # rdb文件保存的目录
主从复制 REPLICATION
安全 SECURITY
requirepass 你的密码 # 设置密码(默认被注释着需要自己解开注释)
当然可以通过命令行配置(临时,服务重启失效)
客户端限制
# maxclients 10000 # 限制最多10000个客户端访问(默认注释)
内存管理
# maxmemory # 最大内存设置(默认注释)# maxmemory-policy noeviction # 内存达到上限之后的处理策略(默认noeviction) # 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) # 2、allkeys-lru : 删除lru算法的key # 3、volatile-random:随机删除即将过期key # 4、allkeys-random:随机删除 # 5、volatile-ttl : 删除即将过期的 # 6、noeviction : 永不过期,返回错误
APPEND ONLY MODE (AOF配置)
appendonly no # 默认是不开启aof的,默认使用rdb方式持久化appendfilename "appendonly.aof" # 持久化的文件名# appendfsync always # 每次修改都会同步,销耗性能appendfsync everysec # 每秒执行一次同步,可能会丢失这1秒的数据# appendfsync no # 不同步,操作系统自己同步数据,速度最快
Redis是内存数据库,数据库状态断电及失,因此Redis提供了持久化功能(内存数据写入磁盘)
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。 保存文件格式
dump.rdb
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gtHBBtX4-1642752175457)(https://www.kuangstudy.com/bbs/2020-11-2-Redis%E8%AF%A6%E7%BB%86%E7%AC%94%E8%AE%B0.assets/image-20201117102650803.png)]
重启服务
systemctl restart redis_6379
1.查看rdb文件存放的目录
2.删除已有的dump.rdb文件
3.添加5个key
1.满足redis.conf中,快照save生成条件
2.使用flushall命令,自动生成一个RDB文件
3.退出redis
只需要将RDB文件放入Redis启动目录就可以了,Redis自动加载
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,只许追加文件,不许改写文件。以文本的方式记录,可以打开文件看到详细的操作记录。
保存文件格式
appendonly.aof
aof默认不开启,需要到配置文件中开启
客户端进行一些操作
appendonly.aof文件内容,日志形式记录
如果aof文件大于64mb,就会fork一个新进程来将我们的文件进行重写(清除之前的64mb)
如果aof文件有错误,redis是启动不了的,可以使用官方自带 redis-check-aof --fix aof文件
进行修复
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis客户端可以订阅任意数量的频道
订阅/发布消息图:
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
命令 | 描述 |
---|---|
PSUBSCRIBE pattern [pattern..] |
订阅一个或多个符合给定模式的频道。 |
PUNSUBSCRIBE pattern [pattern..] |
退订一个或多个符合给定模式的频道。 |
PUBSUB subcommand [argument[argument]] |
查看订阅与发布系统状态。 |
PUBLISH channel message |
向指定频道发布消息 |
SUBSCRIBE channel [channel..] |
订阅给定的一个或多个频道。 |
SUBSCRIBE channel [channel..] |
退订一个或多个频道 |
127.0.0.1:6379> subscribe blog # 订阅频道Reading messages... (press Ctrl-C to quit) # 等待推送信息1) "subscribe"2) "blog"3) (integer) 11) "message" # 消息2) "blog" # 消息来自频道3) "hello world!" # 消息内容127.0.0.1:6379> publish blog "hello world!" # 发送消息到频道(integer) 1127.0.0.1:6379>
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
复杂的情况,使用专业的消息中间件
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
一般来说,要将Redis运用与工程项目中,只使用一台Redis是万万不能的(避免宕机,一主二从),原因如下:
只要在公司中,不可能使用单机(有瓶颈),必须配置集群、使用主从复制
只需配置从库,无需配置主库
查看主从复制信息:
默认情况下,每一个Redis服务器都是主节点
127.0.0.1:6379> info replication# Replicationrole:master # 主节点connected_slaves:0 # 没有从节点master_replid:7776dea8df483b02d12cd482d2034ba55ec7dab0master_replid2:0000000000000000000000000000000000000000master_repl_offset:0second_repl_offset:-1repl_backlog_active:0repl_backlog_size:1048576repl_backlog_first_byte_offset:0repl_backlog_histlen:0
主:redis_6379.conf
从:redis_6380.conf、redis_6381.conf
由于端口和后台默认运行pid都是6379的,就不改了
下面以
redis_6380.conf
为例,redis_6381.conf
同理
cd /usr/local/bin # 进入配置文件所在目录[root@liuyou bin]# redis-server redis_6379.conf[root@liuyou bin]# redis-server redis_6380.conf[root@liuyou bin]# redis-server redis_6381.conf[root@liuyou bin]# ps -ef | grep redis # 查看启动状态root 2862 1 0 13:13 ? 00:00:00 redis-server 0.0.0.0:6379root 2868 1 0 13:13 ? 00:00:00 redis-server 0.0.0.0:6380root 2874 1 0 13:13 ? 00:00:00 redis-server 0.0.0.0:6381root 2880 2393 0 13:13 pts/0 00:00:00 grep --color=auto redis
①开启4个窗口,前三用于主从复制,最后一个用于测试
②登录(注意端口)
默认情况下,每一个Redis服务器都是主节点
一般情况下,只配置从机
如果有密码,需要在从配置文件中,进行配置,后面会解释
下面以 窗口3(端口为6381) 为例,窗口2同理
查看主节点,主从复制信息
配置完重启服务
Redis只允许,主机写,从机读
主机写
从机读
从机不能写
主机断开连接,从机依旧连接主机,但是没有写操作,如果从机中途恢复正常,从机依旧可以获取主机写的内容
如果使用命令行配置,从机重启,就会变为主机(只有变为从机,才能获取之前主机内容)
从机断开连接,期间,主机继续写新内容,只要从机恢复,就可获得主机写的新内容
Slave 启动成功连接到Master后会发送一个sync命令
Master接收到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,Master将传送整个数据文件到Slave,并完成一次完全同步
全量复制:而Slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到修改命令依次传给Slave,完成同步
但是只要重新连接Master,一次完全同步(全量复制)将被自动执行。
上一个M连接下一个S,(可以看做主从复制的一种实现)
配置只需:将上面配好的,窗口3的主机改为窗口2即可
slaveof no one # 如果主机断开连接,从机可以使用该命令,让自己变为主机,其他节点连到该节点
如果主机如果会来了,也没有从机连接上它。
自动选举Redis主服务器(如果主服务器宕机)
在 Redis2.8
之前,采用手动配置主机的形式(会导致一段时间服务不可用)
Redis2.8
之后,Redis正是提供了Sentinel(哨兵)来解决这个问题(主机宕机,根据投票自动在从机中选出新主机)
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,他会独立运行**。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例**
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。
当其他哨兵也检测到主服务器不可用,并且达到一定数量时,那么哨兵之间就会进行一次投票,投票的结果有一个哨兵发起,进行failover故障转移操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个时候称为客观下线
sentinel.conf
)文件名不能写错,下面是文件内容(当然这个文件需要自己创建)
# sentinel monitor 被监控的名称 host port 1 # 1 表示 主机宕机,从机投票选举sentinel monitor myredis 127.0.0.1 6379 1
如果被监控有密码 需在
sentinel.conf
配置文件中,追加# sentinel auth-pass 被监控主机名称
sentinel auth-pass myredis
redis-sentinel sentinel.conf
①主机关闭
②哨兵详情
③检查窗口3(端口为6381)
④我们恢复窗口1(即之前的主机),看看情况
变成从机
# Example sentinel.conf# 哨兵sentinel实例运行的端口 默认26379 # 如果有哨兵集群 需要配置多个端口port 26379 # 哨兵sentinel的工作目录dir /tmp# 哨兵sentinel监控的redis主节点的 ip port # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了# sentinel monitor sentinel monitor mymaster 127.0.0.1 6379 1# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码# sentinel auth-pass sentinel auth-pass mymaster MySUPER--secret-0123passw0rd# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒# sentinel down-after-milliseconds sentinel down-after-milliseconds mymaster 30000# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,# 这个数字越小,完成failover所需的时间就越长,# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。# sentinel parallel-syncs sentinel parallel-syncs mymaster 1# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: #1. 同一个sentinel对同一个master两次failover之间的间隔时间。#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。#3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了# 默认三分钟# sentinel failover-timeout sentinel failover-timeout mymaster 180000# SCRIPTS EXECUTION#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。#对于脚本的运行结果有以下规则:#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,#一个是事件的类型,#一个是事件的描述。#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。#通知脚本# sentinel notification-script sentinel notification-script mymaster /var/redis/notify.sh# 客户端重新配置主节点参数脚本# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。# 以下参数将会在调用脚本时传给脚本:# # 目前总是“failover”,# 是“leader”或者“observer”中的一个。 # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的# 这个脚本应该是通用的,能被多次调用,不是针对性的。# sentinel client-reconfig-script sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 运维配置
这里仅仅是了解,不涉及解决方案底层
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它带来了一些问题。其中最要害的问题,就是数据一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
缓存穿透:用户想要查一个数据,发现Redis内存数据库中没有,也就是缓存没命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多时,缓存都没有命中(秒杀!),于是都去请求持久层数据库。这会导致持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
布隆过滤器是一种数据结构,对所有可能查询的参数以Hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免存储系统的查询压力。
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源
但是这个方法会存在两个问题
缓存雪崩,是指在某一时间段,缓存集中过期失效。Redis宕机!
产生原因之一:
比如,双十一零点抢购,会把同一批商品信息比较集中的放入缓存中,假设缓存设置一个小时的过期时间,那么到凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压抑波峰。于是所有的请求就会向存储层,存储层的调用量会暴增,可能造成存储层奔溃,服务器宕机。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个结点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很可能瞬间就把数据库压垮。
这个思想含义是,既然Redis有可能挂到,那我们多增设几台Redis,这样挂掉之后其他的还可以继续工作,其实就是搭建集群
这个解决方案的思想是,在缓存失效后,通过加锁或队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据加热的含义就是在正式部署之前,我们先把数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
这里需要注意和缓存穿透的区别,缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对一个点进行访问,当这个key在失效的瞬间,执行的大并发就穿破缓存,直接请求持久层数据库,就像在一个屏幕上凿开一个洞。
当某个key在过期瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大。
从缓存层面,没有设置过期时间,所以不会出现热点key过期后产生的问题。
分布式锁:使用分布式锁,保证对于每个key同时只能有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此分布式锁的考验很大。
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明,KuangStudy,以学为伴,一生相伴!
本文链接:https://www.kuangstudy.com/bbs/1353692191381356545