Redis笔记

Redis

什么是NoSQL

NoSQL:(Not Only SQL)不仅仅是SQL

泛指非关系型数据库,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发社区!暴露出来很多难以克服的问题,NoSQL在当下大数据环境的发展十分迅速,Redis是发展最快的,是必须要掌握的技术

很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式,不需要多余的操作就可以横向扩展

NoSQL特点

解藕

1、方便扩展(数据之间没有关系,很好扩展)

2、大数据量高性能(Redis每秒可以读取11万次,写8万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)

3、数据类型丰富(不需要事先设计数据库!如果是数据库量十分大的表,很多人就无法设计了)

4、传统RDBMS和NoSQL

传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作操作,数据定义语言
- 严格的一致性
- 基础的事物
- 。。。。。
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理 和 BASE理论(异地多活)
- 保证高性能、高可用、高可扩展
- 。。。。

了解:3V+3高

大数据时代的3V:主要是描述问题的

  • 海量Volume
  • 多样Variety
  • 实时Velocity

大数据时代的3高:对程序的要求

  • 高并发
  • 高可扩(随时可以水平扩展,增加服务器)
  • 高性能(保证用户体验和性能)

真正在公司的实践:NoSQL + RDBMS一起使用才是最强的

NoSQL四大分类

KV键值对:

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + mamecache

文档型数据库(bson格式和json一样):

  • MongoDB(一般必须掌握)
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据库中间的产品,是NoSQL中功能最丰富,最像关系型数据库

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

  • 拓扑图,存放的是关系,比如:朋友圈社交网络,广告推荐
  • Neo4j,InfoGrid
分类 Examples举例 典型应用场景 数据模型 优点 缺点
键值对(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 Key 指向 Value 的键值对,通常用hash table来实现 查找速度快 数据无结构化,通常只被当作字符串或者二进制数据
列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 功能相对局限
文档型数据库 CouchDB, MongoDb Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 查询性能不高,而且缺乏统一的查询语法。
图形(Graph)数据库 Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群

Redis入门

概述

Redis是什么

Redis(Remote Dictionary Server) 远程字典服务

即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API,redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

免费和开源!是当下最热门的NoSQL技术之一!也被人们称为结构数据库!

Redis能干嘛

  1. 内存存储、持久化,内存是断电即失的,所以说持久化很重要(RDB、AOF)
  2. 效率高,可以用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(浏览量!)
  6. 。。。。。

特性

  1. 多样的数据类型

  2. 持久化

  3. 集群

  4. 事物

    。。。。。

学习中需要用到的东西

  1. Redis官网https://redis.io/
  2. Redis中文网http:/www./redis.cn/
  3. 下载地址:通过官网下载(Windows版本在Github上下载)

Redis一般都是安装在Linux系统上的,我们基于Linux学习

Windows安装

下载地址:https://github.com/dmajkic/redis

  1. 解压安装包
    Redis笔记_第1张图片

  2. 开启redis-server.exe

  3. 启动redis-cli.exe测试

Redis笔记_第2张图片

Linux安装

  1. 下载安装包!redis-5.0.8.tar.gz

  2. 解压Redis的安装包!程序一般放在 /opt 目录下

  3. 基本环境安装

    yum install gcc-c++
    # 然后进入redis目录下执行
    make
    # 然后执行
    make install
    

    使用的是5.0.8版本,高版本会有错误

  4. redis默认安装路径 /usr/local/bin

Redis笔记_第3张图片

  1. 将redis的配置文件复制到 程序安装目录 /usr/local/bin/redisconfig下,以后使用这个配置文件启动

  2. redis默认不是后台启动,需要修改配置文件

    修改配置文件中的daemonize将原来的no改为yes即可

  3. 通过制定的配置文件启动redis服务

Redis笔记_第4张图片

  1. 使用redis-cli测试连接

Redis笔记_第5张图片

  1. 查看redis的进程是否开启

在这里插入图片描述

  1. 关闭Redis服务

在这里插入图片描述

  1. 再次查看进程是否存在

在这里插入图片描述

可以发现客户端进程已经结束,只剩下了redis的服务

测试性能

序号 选项 描述 默认值
1 -h 指定服务器主机名 127.0.0.1
2 -p 指定服务器端口 6379
3 -s 指定服务器 socket
4 -c 指定并发连接数 50
5 -n 指定请求数 10000
6 -d 以字节的形式指定 SET/GET 值的数据大小 2
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个并发连接 1000000个请求
redis-benchmark -h locl -p 6379 -c 100 -n 1000000 

Redis基本命令

redis默认有16个数据库

select 1    	#切换数据库
dbsize			#查看数据库大小
Set name haiyang #存放数据
get name 		#读取数据
keys *			#查看所有的key
flushdb 		#清空当前数据库
flushall		#清空所有数据库
Exist name 		#判断一个key是否存在
move name		#移除一个key
expire name 10  #设置元素10秒后过期
type name       #查看元素的类型
append name 123 #在当前元素后面追加,如果key不存在就新建一个key
strlen key      #获取字符串的长度
incr views 		#设置自增
decr views		#设置自减
incrby views 10 #自增10
decrby views 10 #自减10
getrange name 0 3 #获取字符串下标从0到3(闭区间)
gerrange name 0 -1 #获取全部字符串
setrange name 1 xx #从下标1开始替换为xx
setex name 30 "hello" #设置过期时间
setnx name "abc" #如果不存在进行设置,如果存在返回0 (分布式锁中会常常使用)
mset k1 v1 k2 v2 k3 v3 #设置多个元素
mget k1 k2 k3		#获取多个值
msetnx k1 v1 k4 v4  #设置多个值同时,如果不存在返回0
mset user:1:name zhangsan user:1:age 10
mget user:1:name user:1:age
getset name zhangsan #先get再set,如果不存在,返回null,如果存在,获取原来的值,并设置新的值

为什么redis是6379,redis创始人的偶像

redis是单线程的!

Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的平静是根据机器的内存和网络带宽,所以redis使用单线程

Redis是C语言写的,官方数据,每秒10w+ QPS,不比Memecache差!

Redis为什么单线程还这么快?

单线程不用上下文切换,CPU上下文切换很耗费性能

核心:将所有的数据全部放在内存中的,所以所使用单线程操作效率就是最高的,多线程

(CPU上下文切换,是耗时的操作),对于内存系统来说,没有上下文切换效率就是最高的。

Redis五大数据类型

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)。

String(字符串)

String类型的使用场景:value除了是字符串还可以是数字!

  • 计数器
  • 统计多单位的数量
  • 对象缓存存储

List

基本的数据类型,列表

在Redis里面,我们可以把List当成栈、队列、阻塞队列来理解

所有的List命令都是L或者R

LPUSH list 1 			#在左侧插入一个或多个值
LPUSH list 2
LPUSH list 3
LRANGE list 0 -1		#取所有数据	
LRANGE list 0 1			#取0到1两个数据
######################################################
RPUSH list 1		#在右侧插入一个或多个值
######################################################
Lpop list 			#移除list左边第一个元素
Rpop list 			#移除list的右边一个元素
######################################################
Lindex list 1		#获取下标为1的值
Llen list			#查询list的长度
######################################################
Lrem list 1 value	#移除list中的1个value
Lrem list 2 value	#移除list中的2个value
######################################################
trim list 1 2		#通过下标截取指定的长度,这个list已经被改变了,只剩下截取的元素
rpoplpush list1 list2 #把list1最后一个取出放到list2的头部
lset list 0 123		#将list的下标为0的元素设置为123(如果不存在列表会报错)
######################################################
linsert list before "hello" "other" #在一个元素前面插入一个元素
linsert list after "hello" "other" #在一个元素后面插入一个元素

小结

  • Redis中的List实际上是一个链表,在链表前面和后面都可以插入值
  • 如果不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有的值,空链表,也代表不存在
  • 在两边插入或者改动值效率最高,在中间插入效率会低一点

消息队列,队列(lpush rpop),栈(lpush lpop)

Set(集合)

set中的值不能重复

sadd myset "hello"  #放入一个元素
smembers myset 		#查看所有元素
sismember myset "hello" #判断myset中是否存在"hello"
scard myset			#查看set中元素的个数
srem myset "hello"  #删除指定元素
#################################################
srandmember myset 	#随机获取一个元素
spop myset			#随机删除一个元素
#################################################
smove myset myset2 "hello" #将一个指定的值移动到另外一个集合中
sdiff key1 key2 	#查看key1中存在而key2中不存在的值
sinter key1 key2	#查看key1中和key2中都有的值
sunion key1 key2	#查看key1中key2中所有的值,并去重

Hash(哈希)

Map集合,key-map 这个时候这个值是一个map集合,hash本质和String类型没有太大区别,还是一个简单的key-value

hset myhash field1 123	#set 一个具体 key-value
hget myhash field1 
hset myhash field1 hello field2 world	#插入多个key-value
hget myhash field1 field2	#获取两个字段值
hgetall myhash				#获取所有字段值
hdel myhash field1 			#删除myhash中field1和对应的value
hlen myhash 				#查看长度
hexists myhash field1		#判断hash中指定字段是否存在
hkeys myhash				#只获得所有的key
hvalues myhash				#只获得所有的value
hincrby myhash field3 1		#使指定key对应的value自增1
hdecrby myhash field3 1		#使指定key对应的value自减1
hsetnx myhash field4 hello	#创建一个key-value如果存在则不创建

hash变更的数据 user name age 尤其是用户信息,经常变动的信息!hash更加适合对象的存储,而String更适合字符串的存储

ZSet(有序集合)

在set的基础上,增加了一个值,set k1 v1 set k1 score1 v1

zadd myset 1 one			#插入一个值
zrange myset 0 -1			#查看所有值
#################################################
zrangebyscore salary -inf +inf whithscores ##显示全部数据,按升序排列
zrangebyscore salary -inf 2500 whithscores #显示score小于2500,按升序排列
zrevrange salary 0 -1 					   ##显示全部数据,按降序排列
#################################################
zrem salary xiaohong		#移除有序集合中的指定元素
zcard salary 				#获取有序集合中元素个数
zcount myset 1 3			#获取指定区间的元素数量

其余的一些API,如果工作中有需要,可以查看官方文档

案例思路:set 排序 存储班级成绩表,工资表排序,排行榜应用实现

普通消息,1 ,重要消息 2,带权重进行判断

三种特殊数据类型

geospatial 地理位置

Redis的Geo在Redis3.2版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的距离

可以查询一些地理位置数据:https://jingweidu.bmcx.com

Geo只有6个命令

geoadd china:city 116.40 39.90 beijing #插入北京的地理位置(经纬度)
#地球的南北极位置无法添加,我们一般会下载城市数据,通过java程序直接一次性导入
#参数值(经度,纬度,名称)
#有效的经度从-180度到180度。
#有效的纬度从-85.05112878度到85.05112878度。
geopos china:city beijing 				#获取指定的经纬度

两点之间的距离

geodist china:city beijing shanghai 	#查看两点之间的直线距离

附近的人(获得所有附近的人的地址,定位!)通过半径来查询

georadius china:city 116.40 39.90 1000 km withdist withcoord count 1 #以给定的经纬度为中心,找出附近的城市WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。WITHCOORD: 将位置元素的经度和维度也一并返回。WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:ASC: 根据中心的位置, 按照从近到远的方式返回位置元素。DESC: 根据中心的位置, 按照从远到近的方式返回位置元素。COUNT: 筛选出固定个数的元素

找出位于指定元素周围的其他元素!(用于地图)

georadiusbymember china:city beijing 1000 km

geohash(返回11位的GeoHash字符串)

#将二维的经纬度转换为一维的字符串,如果两个字符串越像越接近geohash chin:city beijing shanghai 

Geo的底层实现原理就是ZSet,删除ZSet中的元素Geo中的元素也就删除了

Hyperloglog

什么是基数? 基数就是数据集中去重后的元素个数

A{1,2,3,4,5,6,7}

B{3,5,6,8,9,0,0}

A的基数是7,B的基数是6

简介

Redis 2.8.9更新了Hyperloglog数据结构!

Redis Hyperloglog基数统计的算法

优点:占用的内存是固定的,2^64不同的元素基数,只需要占用12KB内存!从内存角度考虑Hyperloglog是首选

网页的UV(一个人访问网站多次,还是算做一个人)

传统方式,用set保存用户的id,统计set中的元素数量作为标准判断!

这个方式如果保存大量的用户id,就会比较麻烦!目的是为了计数,而不是保存id

0.81%错误率!统计UV任务,可以忽略不计

PFadd mykey a b c d e f c q 			#存入元素PFadd mykey2 3 2123 323 22 23 5 9 6 4PFcount mykey											#统计元素基数数量pmerge mykey3 mykey2 mykey 				#合并两组到mykey3

如果允许容错,那一定可以使用Hyperloglog

如果不允许容错,就使用set集合或其他数据类型即可

Bitmap

位存储

统计疫情感染人数:0 1 0 0 0 1

统计用户信息,活跃,不活跃!登陆、未登录!打卡,365打卡!两个状态的都可以使用Bitmaps

Bitmaps 位图,数据结构!所有操作都是操作二进制位来进行记录,就只有0和1两种状态

365天 = 365bit 1字节=8bit 46个字节左右!

测试

setbit sign 0 0setbit sign 1 1setbit sign 2 1setbit sign 3 1setbit sign 4 0setbit sign 5 0setbit sign 6 1						#记录周一到周日的打卡getbit sign 1							#查看周二的打卡情况

统计操作

bitcount sign							#统计本周打卡记录

事务

事务的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行

一次性、顺序性、排他性!执行一系列的命令!

Redis事务是没有隔离级别的概念

所有命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Execute

Redis单条命令是保证原子性的,但是事务不保证原子性

redis的事务

  • 开启事务(multi)
  • 命令入队(…)
  • 执行事务(execute)

正常执行事务!

multi		#开启事务#命令入队set k1 v1set k2 v2exec	#执行事务

放弃事务

multi		#开启事务#命令入队set k1 v1set k2 v2discard	#放弃事务

Redis不允许编译型异常(代码有错误,编译不通过)

Redis允许运行时异常(编译通过,运行报错)如果事务队列中存在语法性问题,有异常的语句会抛出异常,但不会影响其他语句执行

监控! Watch

悲观锁:

  • 很悲观,认为什么时候都会出问题,无论做什么都会加锁!

乐观锁:

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

Redis监视测试

set money 100set out 0watch money			#监视money对象multidecrby money 20incrby out 20exec

测试多线程修改值,使用watch可以当redis的乐观锁操作

set money 100watch moneymultidecrby money 10incrby out 10exec						#在exec执行之前,另一个线程修改money的值,事务执行会失败,返回(nil)

事务执行失败或者成功会自动放弃监视

如果修改失败,获取最新的值就好

Redis的watch监视的是对应元素的version,而不是值,所以即便发生ABA问题(狸猫换太子),事务执行还是会报错

Jedis

我们要用java来操作Redis

什么是jedis,是Redis官方推荐的java连接开发工具!使用java操作Redis中间件!如果你要使用java操作redis,那么一定要对jedis十分的熟悉!

测试

导入依赖:


<dependency>
  <groupId>redis.clientsgroupId>
  <artifactId>jedisartifactId>
  <version>3.3.0version>
dependency>

配置:

Jedis jedis = new Jedis("codekitty.cn", 6379);

使用:

public static void main(String[] args) {
  Jedis jedis = new Jedis("codekitty.cn", 6379);
  
  String set = jedis.set("key1", "value1");
  System.out.println(set);
  String key1 = jedis.get("key1");
  System.out.println(key1);
}

基本的API

和之前的Linux命令行一样,只是换成了方法,此处省略

Jedis操作事务(与命令行操作类似)

Jedis jedis = new Jedis("codekitty.cn", 6379);
        jedis.flushDB();					//先清空所有keys
        System.out.println(jedis.ping());
        Transaction multi = jedis.multi();

        try {
            multi.set("key1","value1");
            multi.set("key2","value2");
            int i = 1/0;							//创建一个异常
            multi.set("key3","value3");
            multi.exec();
        }catch (Exception e){
            multi.discard();	//如果产生异常则放弃事务
            e.printStackTrace();
        }finally {
            System.out.println(jedis.keys("*"));
            System.out.println(jedis.get("key1"));
            jedis.close();		//最终关闭jedis连接
        }

SpringBoot整合

SpringBoot操作数据:spring-data jpa jdbc mongdb redis!

SpringData也是和SpringBoot齐名的项目!

在SpringBoot2.x之后原来使用的Jedis被lettuce替换了?

jedis:采用的是直连,多个线程操作不安全,如果想要避免不安全的,要使用jedis pool连接池!BIO

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数量,NIO

源码分析:

@Bean
	@ConditionalOnMissingBean(name = "redisTemplate") //这个方法在开发者没有自己定义的情况下生效
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {//默认的Redistemplate没有过多的设置,redis都是需要序列化的
    //两个范型都是Object类型,我们使用时需要强制转换,我们可以之际创建一个RedisTemlplate来替代默认的
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {//由于String在Redis中是最常用的类型,所以单独创建一个方法
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

整合测试

1、导入依赖:

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

2、编写配置文件:

spring: #配置redis
  redis:
    host: codekitty.cn
    port: 6379
#   lettuce: 如果需要配置连接池,尽量使用lettuce进行配置 

3、连接测试

@Autowired
RedisTemplate redisTemplate;

@Test
void contextLoads() {
  //操作连接
  RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
  try {
    connection.flushDb();
    connection.flushAll();
    //插入元素
    redisTemplate.opsForValue().set("key1", "value1");
    redisTemplate.opsForList().leftPush("list1","value");
    //操作事务
    redisTemplate.watch("key1");
    redisTemplate.multi();
    redisTemplate.exec();
  }catch (Exception e){
    redisTemplate.discard();
    e.printStackTrace();
  }finally {
    connection.close();
  }
}

自定义RedisTemplate

创建RedisConfig.java并将源码中的配置拷贝过来,然后修改,添加自己需要的配置

@Configurationpublic class RedisConfig {    /**     * 固定模版     * @param factory     * @return     */    @Bean    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);        //为了使用方便,直接使用        RedisTemplate template = new RedisTemplate<>();        ObjectMapper objectMapper = new ObjectMapper();        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();        objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);        //key的序列化使用string方式        template.setKeySerializer(stringRedisSerializer);        template.setConnectionFactory(factory);        //value的序列化使用jackon方式        template.setValueSerializer(objectJackson2JsonRedisSerializer);        //hash 的key也使用string的序列化方式        template.setHashKeySerializer(stringRedisSerializer);        //hash 的value也使用jackon的序列化方式        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);        template.afterPropertiesSet();        return template;    }}

企业级开发可以封装RedisUtil方便使用

Redis.conf配置

redis启动的时候通过配置文件来启动

单位

# Redis configuration file example.
# 
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

配置文件对大小写不敏感

包含

################################## INCLUDES ###################################

# Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

################################## MODULES #####################################

可以把其他配置文件引用过来

网络

################################## NETWORK #####################################

bind 127.0.0.1 -::1
# Protected mode is a layer of security protection, in order to avoid that
# Redis instances left open on the internet are accessed and exploited.
#
# When protected mode is on and if:
#
# 1) The server is not binding explicitly to a set of addresses using the
#    "bind" directive.
# 2) No password is configured.
#
# The server only accepts connections from clients connecting from the
# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
# sockets.
#
# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode yes  #是否受保护
port 6000						#端口

通用配置

################################# GENERAL #####################################

# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
# When Redis is supervised by upstart or systemd, this parameter has no impact.
daemonize yes		#守护线程方式是否开启(后台运行)

pidfile /var/run/redis_6379.pid	#如果以后台方式运行,我们就需要指定一个pid文件

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing) 一般用于开发测试阶段
# verbose (many rarely useful info, but not a mess like the debug level) 和debug很像
# notice (moderately verbose, what you want in production probably)	生产环境日志级别
# warning (only very important / critical messages are logged)		关键信息才会打印
loglevel notice		#日志级别

# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""			#日志的文件位置名

databases 16			#默认的数据库数量

# By default Redis shows an ASCII art logo only when started to log to the
# standard output and if the standard output is a TTY and syslog logging is
# disabled. Basically this means that normally a logo is displayed only in
# interactive sessions.
#
# However it is possible to force the pre-4.0 behavior and always show a
# ASCII art logo in startup logs by setting the following option to yes.
always-show-logo yes		#是否显示logo

set-proc-title yes	

proc-title-template "{title} {listen-addr} {server-mode}"

快照

在规定的时间内进行了多少次操作会持久化道 .RDB .AOF

redis是内存数据库,没有持久化就会断电即失

save 3600 1			#如果3600秒内有一个key进行了修改,就进行持久化操作
save 300 100		#300秒内有超过100个key进行了修改,就进行持久化操作
save 60 10000		#............
#之后学习持久化会按自己的实际情况进行配置

stop-writes-on-bgsave-error yes 	#如果持久化出错了是否还要继续工作!

rdbcompression yes			#是否压缩RDB文件,需要消耗一些CPU的资源
rdbchecksum yes					#是否在保存RDB文件的时候进行检查校验
dir ./					#RDB文件保存的目录

主从复制 REPLICATION

################################# REPLICATION #################################

# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
#   +------------------+      +---------------+
#   |      Master      | ---> |    Replica    |
#   | (receive writes) |      |  (exact copy) |
#   +------------------+      +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
#    stop accepting writes if it appears to be not connected with at least
#    a given number of replicas.
# 2) Redis replicas are able to perform a partial resynchronization with the
#    master if the replication link is lost for a relatively small amount of
#    time. You may want to configure the replication backlog size (see the next
#    sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
#    network partition replicas automatically try to reconnect to masters
#    and resynchronize with them.
#
replicaof <masterip> <masterport>			#配置主机

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the replica to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the replica request.
#
masterauth <master-password>					#配置主机密码

安全

requirepass 123456		#设置密码 默认没有密码

config set requirepass "123456"	#命令行设置密码
auth 123456						#使用密码进行登录

客户端限制

maxclients 10000 		#设置最大连接数限制

maxmemory <bytes>		#设置最大的内存容量

maxmemory-policy 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 AOF配置

appendonly no			#默认不开启(默认使用RDB方式持久化)
appendfilename "appendonly.aof"		#持久化的文件名

# appendfsync always	#每次修改都会同步,消耗性能
appendfsync everysec	#每秒执行一次	可能会丢失这1秒的值
# appendfsync no			#不执行

Redis持久化

Redis是内存数据库,一旦断电,数据即失去,所以Redis也提供了数据持久化功能

RDB(Redis DataBase)

什么是RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程
都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那
RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是
RDB,一般情况下不需要修改这个配置!

dbfilename dump.rdb		#rdb默认文件名
save 60 5							#60秒内修改了5次就会进行持久化

触发机制

  1. save的规则满足时会触发rdb机制
  2. flushall会触发rdb机制
  3. 退出redis会触发rdb机制

如何恢复rdb文件

只需将rdb文件放入redis启动目录就可以了,redis启动的时候会自动恢复rdb文件的数据

127.0.0.1:6379> config get dir
1) "dir"
2) "/Users/mac"			#如果在这个目录下有dump.rdb文件,edis启动的时候会自动恢复其中的数据

默认配置即可

在生产环境会对rdb文件进行备份!

优点:

  1. 适合大规模的恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次修改的数据就没有了
  2. fork进程的时候会占用一定空间

AOF (Append Only File)

以日志形式将所有操作记录下来保存为.aof文件,恢复的时候就把这个文件全部执行一遍

是什么

aof配置:

############################## APPEND ONLY MODE ###############################


appendonly no			#默认不开启,需要手动设置为yes

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"


# appendfsync always		#持久化策略,每次修改都写入
appendfsync everysec		#持久化策略,每秒写入
# appendfsync no				#持久化策略,不写入

no-appendfsync-on-rewrite no		#是否重写,不重写可以保证安全性

auto-aof-rewrite-percentage 100	#默认即可
auto-aof-rewrite-min-size 64mb	#当文件超过64mb会创建一个新的文件

将appendonly改为yes即为开启aof

重启服务:

shutdown

redis-server ./redis.conf

写入后的.aof文件被修改,则无法启动redis-server服务,redis提供了这样一个工具(Redis-check-aof)

redis-check-aof --fix appendonly.aof		#执行此命令会自动修复.aof文件,但会删除掉错误的命令!!

优点和缺点

优点:

  1. 每一次修改都同步,让文件的完整性更好

缺点:

  1. 如果是每秒同步一次,会丢失一秒的数据
  2. 数据文件体积远远大于rdb,修复也比rdb慢,所以redis默认的配置是rdb,而不是aof

扩展:

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

  • aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大

  • 如果只做缓存不需要使用任何持久化

  • 同时开启两种持计划方式,重启时优先载入aof文件来恢复数据
    只会找aof文件,但是推荐只使用rdb用于备份,能快速重启,并且不会有aof可能潜在的bug
    性能建议

  • rdb文件只做后背用途,建议只在slave上持久化rdb文件,15分钟备份一次,使用save 900 1 规则

  • 使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据

  • 代价:持续的io
    rewrite 过程中产生的新数据写到新文件造成的阻塞不可避免,尽量减少rewrite的频率

  • 不使用aof,也可以通过Master-Slave Replication 实现高可用性也可以,能省去一大笔io,减少rewrite带来的系统波动

    代价:如果Master-Slave 同时倒掉,回丢失十几分钟的数据,启动脚本也要比较Master-Slave中的rdb文件,选择最新的文件,载入新的,微博就是这种架构

发布订阅

Redis发布订阅(pub/sub)是一种通信模式:发送者(pub)发送消息,接收者(sub)接收消息,微博,微信。。。

Redis客户端可以订阅任何数量的频道

发布订阅消息图:

1、消息发送者

2、频道

3、消息接收者

Redis笔记_第6张图片

Redis 发布订阅命令

下表列出了 redis 发布订阅常用命令:

序号 命令及描述
1 [PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。
2 [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3 PUBLISH channel message 将信息发送到指定的频道。
4 [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5 [SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6 [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。

测试

127.0.0.1:6379> psubscribe mychannel					#订阅频道
Reading messages... (press Ctrl-C to quit)		#等待读取推送的信息

127.0.0.1:6379> publish mychannel "send a message"	#发布消息

				#订阅端接收到消息
1) "pmessage"								#消息
2) "mychannel"							#哪个频道的消息
3) "mychannel"
4) "send a message"					#消息的内容

原理

每个Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

使用场景

  1. 实时消息系统
  2. 实时聊天(频道作为聊天室)
  3. 订阅、关注系统

稍微复杂的场景会使用消息中间件来做(RabbitMQ…)

主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用基石:主从复制还是哨兵和集群能够实施的基础。

不能只使用一台redis的原因:

  • 从结构上讲,单个redis服务器会发生单点故障,一台服务器需要处理所有请求,压力大
  • 从容量上讲,单个redis服务器内存容量有限,并且不能完全使用全部的内存,单台redis的最大内存不应该超过20g

通常的电商网站都是一次上传吗,无数次浏览,读多写少 ,主从复制,读写分离,80%的情况都在进行读操作,起码一主二从

只要是在公司中使用,主从复制必须使用,不可能使用单机

环境配置

只配置从库,不用配置主库

redis-server #启动主服务

info replication		#查看当前节点信息

# Replication
role:master						#当前节点信息
connected_slaves:0		#从机个数
master_failover_state:no-failover
master_replid:906a84bfb098cf9ddfc3cfc39b5b26419a644a77
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. 端口号
  2. pid
  3. 日志名
  4. 备份名

修改后启动三个redis服务

redis-server ./redis.conf
...

主从复制配置

默认情况下每个节点都是主节点,一般只要配置从机就可以了

配置

slaveof 127.0.0.1 6000		#将本节点设置为本机6000端口的从机

真实的主从配置应该在配置文件配置,这里使用的是命令,重启会失效,配置规则在之前的Redis.conf章节

细节

  • 主机负责写,从机负责读!主机中的所有数据都会被从机复制,从机写会报错
  • 主机崩掉从机会保存数据
  • 主机崩掉重新连接之后,从机依然可以读取到主机set的值
  • 如果使用命令行配置主从,重启后默认作为主机启动,重新设置为主机的从机后会自动同步数据(全量复制),如果在配置文件中设置主机,启动时自动同步主机的数据

从机第一次连接会同步所有数据:全量复制

后续的每一次操作同步至从机:增量复制

主从复制的其他实现

上一个Master连接下一个Slave(既当主机又当从机,但此时这个节点不能写入,只有在主节点挂掉之后,使用slaveof no one这个命令让该节点作为主节点进行写入)

这是哨兵模式没有出现的时候的方法

哨兵模式

概述

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

单机哨兵模式

img

哨兵的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

多哨兵模式

img

假设master宕机,sentinel先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,sentinel之间会发起一次投票,投票的结果由随机一个sentinel发起,进行failover操作,得到sentinel票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线

测试

目前的状态是一主二从

  1. 配置哨兵配置文件sentinel.conf
##sentinel monitor 监控的名称 监控的地址 监控的端口 1
sentinel monitor myredis 127.0.0.1 6000 1 		

后面的1代表如果主机挂了让从机投票,按票数选定新主机

  1. 启动哨兵
redis-sentinel ./conf/sentinel.conf		#按配置文件启动哨兵
  1. 哨兵监控到的信息:
3128:X 03 Apr 2021 16:14:53.825 # +monitor master myredis 127.0.0.1 6000 quorum 1			#主机为本机6000端口
#有两个从机		分别为本机6001端口和6002端口
3128:X 03 Apr 2021 16:14:53.827 * +slave slave 127.0.0.1:6001 127.0.0.1 6001 @ myredis 127.0.0.1 6000	
3128:X 03 Apr 2021 16:14:53.828 * +slave slave 127.0.0.1:6002 127.0.0.1 6002 @ myredis 127.0.0.1 6000
  1. 测试如果主机崩掉
#哨兵日志:
3128:X 03 Apr 2021 16:19:57.771 # +failover-end master myredis 127.0.0.1 6000			监控到6000端口断开连接
3128:X 03 Apr 2021 16:19:57.771 # +switch-master myredis 127.0.0.1 6000 127.0.0.1 6002		自动投票选举,6002选举为主机
3128:X 03 Apr 2021 16:19:57.771 * +slave slave 127.0.0.1:6001 127.0.0.1 6001 @ myredis 127.0.0.1 6002	#将6001的主机设置为6002
3128:X 03 Apr 2021 16:19:57.771 * +slave slave 127.0.0.1:6000 127.0.0.1 6000 @ myredis 127.0.0.1 6002 #将6000的主机设置为6002,在6000端口重新连接时可以直接同步主机的数据

如果主机挂掉后重新连接,原来的主机会作为从机

优点:

  1. 哨兵集群基于主从复制,所有主从配置的优点,它都有
  2. 主从可以切换,故障可以转移,系统的可用性就会更好
  3. 哨兵模式就是主从模式的升级,从手动到自动,更加健壮

缺点:

  1. redis不好做在线扩容,集群容量达到上限,在线扩展非常麻烦
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多配置

哨兵的全部配置

# 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)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击

解决方案

  • 布隆过滤器

    对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

在这里插入图片描述

  • 缓存空值
在这里插入图片描述

缓存击穿(量太大,一个点的缓存过期)

概念

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

设置热点数据永不过期

这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

加互斥锁(分布式锁)

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

缓存雪崩(缓存在某一时间缓存集体失效)

概念

产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机

双十一时会停掉一些服务,保证主要的一些服务可用,比如:退款服务

在这里插入图片描述

解决方案:

  • 异地多活

    增加集群中服务器数量

  • 限流降级
    缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待

  • 数据预热
    正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀

小结

你可能感兴趣的:(数据库,redis,nosql,数据库)