缓存
为了解決不同介质的响应速度和成本差异的问题。不同介质包括磁盘、内存、网络等。缓存的使用可以提高系统的读取速度和性能。
数据存储介质 | 寻址 | I/O buffer带宽 | 持久性 |
---|---|---|---|
磁盘 | ms | G/M | 持久 |
内存 | ns | 很大 | 断电丢失 |
s秒>ms毫秒>us微秒>ns纳秒 磁盘比内存在寻址上慢了10W倍。
磁盘=磁道+扇区。一扇区 512Byte,带来一个成本变大:索引。在操作系统中,无论你读多少,从磁盘拿数据最少4k。
解决方案:利用缓存折中
选择折中方案的原因:2个基础设施
1)冯诺依曼体系的硬件限制
2)以太网,tcp/ip的网络
缓存类型 | 优点 | 缺点 | 示例 |
---|---|---|---|
本地缓存 | 速度快,无额外IO | 集群模式下一致性难以保证,容量有限,数据丢失 | Guava |
分布式缓存 | 容量大,可集中管理,缓存维度数据一致性 | 网络IO,QPS受限 | Redis |
多级缓存 | 解决了本地缓存和缓存中间件的问题 | 复杂度较高,维护成本高 | JetCache |
本地缓存
GuavaCache
缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。
Guava Cache与ConcurrentMap的区别:
他们很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。
通常来说,Guava Cache适用于:
你愿意消耗一些内存空间来提升速度。
你预料到某些键会被查询一次以上。
缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
分布式缓存
Redis
Redis:REmote DIctionary Server(远程字典服务器)
默认端口:6379
-
线程安全。因为work线程是单线程(避免上下文切换,无需创建/销毁线程,无资源竞争的问题)
-
效率高。
1)纯内存操作的Key-Value。
2)I/O多路复用。启动一个select选择器线程,高并发请求都去选择器线程中,选择器通过轮训得到请求数据,并写到缓存区中,epool解决空轮询问题。
3)5中数据类型,每个类型存在一个本地方法,计算向数据端移动。
中文网: redis.cn
官网: redis.io
jedis: https://github.com/redis/jedis
lettuce: https://github.com/lettuce-io/lettuce-core
spring集成redis: https://spring.io/projects/spring-data-redis
使用场景 |
---|
缓存热点数据,减轻数据库访问压力 |
短信验证码,订单等有效期。key失效回调。 |
分布式锁 |
token令牌。替换session,因为保存在当前jvm中,所以无法做分布式集群,把session存储到redis中。 |
网页计数器hypperloglog统计uv |
Redis命令
性能测试
Redis秒级10万操作,关系型数据库秒级1K操作
------------------------------------------------------------------------
##连接本地redis服务
redis-benchmark
# 例子:测试host为localhost 端口为6379 100个并发连接,100000个请求 的redis服务器性能
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
选项 | 描述 | 默认值 |
---|---|---|
-h | 指定服务器主机名 | 127.0.0.1 |
-p | 指定服务器端口 | 6379 |
-s | 指定服务器 socket | |
-c | 指定并发连接数 | 50 |
-n | 指定请求数 | 10000 |
-d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
-k | 1=keep alive 0=reconnect | 1 |
-r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
-P | 通过管道传输 请求 | 1 |
-q | 强制退出 redis。仅显示 query/sec 值 | |
–csv | 以 CSV 格式输出 | |
-l | 生成循环,永久执行测试 | |
-t | 仅运行以逗号分隔的测试命令列表。 | |
-I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
连接
------------------------------------------------------------------------
##连接本地redis服务
redis-cli
------------------------------------------------------------------------
##连接远程redis服务
redis-cli -h host -p port -a password
------------------------------------------------------------------------
##连接到redis后,检测 redis 服务是否启动
PING
------------------------------------------------------------------------
##授权密码
AUTH password
数据库
------------------------------------------------------------------------
##在某些场景下,可能多个应用同时使用一个redis,那我们希望不同应用的redis数据是隔离的,这时就可以采用设置不同redis数据库的方式
##查询数据库
config get databases
------------------------------------------------------------------------
##切换数据库
select 数据库编号
##注:redis.conf中默认是0号数据库,最大是16
------------------------------------------------------------------------
##查看数据库大小
dbsize
------------------------------------------------------------------------
##清空当前数据库
flushdb
------------------------------------------------------------------------
##清空全部数据库
flushall
key-value
------------------------------------------------------------------------
##设置k-v并且设置过期秒数
setex key 到期秒数
set key ex 到期秒数
------------------------------------------------------------------------
##设置k-v并且设置过期毫秒数
psetex key 到期毫秒数
------------------------------------------------------------------------
##移动key到其他数据库,当前库会删除这个key
move key 数据库编号
------------------------------------------------------------------------
##查询value
get key
------------------------------------------------------------------------
##查询所有的key
keys *
------------------------------------------------------------------------
##查看key的数据类型
type key
------------------------------------------------------------------------
##判断key是否存在
exists key
过期时间
------------------------------------------------------------------------
##设置k-v并且设置过期秒数
setex key 到期秒数
------------------------------------------------------------------------
##设置k-v并且设置过期毫秒数
psetex key 到期毫秒数
------------------------------------------------------------------------
##将键的过期时间设为 ttl 秒
EXPIRE KEY TTL
------------------------------------------------------------------------
##将键的过期时间设为 ttl 毫秒
PEXPIRE KEY TTL
------------------------------------------------------------------------
##将键的过期时间设为 秒时间戳
EXPIREAT KEY timestamp
------------------------------------------------------------------------
##将键的过期时间设为 毫秒时间戳.
PEXPIREAT KEY timestamp
------------------------------------------------------------------------
##KEY永不过期:一)没有设置过期时间。二)移除过期时间
##移除过期时间。
persist KEY
------------------------------------------------------------------------
## 查看还有多少秒过期。-1 表示永不过期,-2 表示已过期,其他数字代表指定键的剩余生存秒数
ttl KEY
------------------------------------------------------------------------
## 查看还有多少秒过期。-1 表示永不过期,-2 表示已过期,其他数字代表指定键的剩余生存毫秒数
pttl KEY
Redis数据类型
key数据类型 | 底层数据结构 | |
---|---|---|
String | 字符串 | SDS简单动态字符串 |
1)在O(1)的时间复杂度中获取字符串长度。
C字符串不记录自身的长度,所以为了获取一个字符串的长度程序必须遍历这个字符串,直至遇到’0’为止,整个操作的时间复杂度为O(N)。
2)二进制安全的。
SDS使用len属性的值判断字符串是否结束。
3)减少了内存重分配次数
空间预分配策略:在增长过程中不会频繁的进行空间分配。
情性空间释放机制:字符串缩短时,只更新 SDS 的len属性,多出来的空间供将来使用。并不立即使用内存重分配来回收缩短后多出来的空间
4)自动扩容机制
扩容阶段:
若 SDS 中剩余空闲空间 avail 大于新增内容的长度 addlen,则无需扩容;
若 SDS 中剩余空闲空间 avail 小于或等于新增内容的长度 addlen:
若新增后总长度 len+addlen < 1MB,则按新长度的两倍扩容;
若新增后总长度 len+addlen > 1MB,则按新长度加上 1MB 扩容。
内存分配阶段:
根据扩容后的长度选择对应的 SDS 类型:
若类型不变,则只需通过 s_realloc_usable扩大 buf 数组即可;
若类型变化,则需要为整个 SDS 重新分配内存,并将原来的 SDS 内容拷贝至新位置。
Value数据类型 | 底层数据结构 | |
---|---|---|
String | 字符串 | int整型、embstr编码的简单动态字符串、raw编码的简单动态字符串 |
List | 列表 | linkedlist双向链表,ziplist压缩表 |
Hash | 哈希 | ziplist压缩表,hashtable(对应Java的HashMap) |
Set | 集合 | intset整数集合,hashtable(对应Java的HashMap) |
Zset | 有序集合 | ziplist压缩表,skiplist跳表 |
geo | 地理位置 | |
hyperloglog | 基数统计 | |
bitmap | 位图 |
ziplist压缩表
当hash,list, zset,set等结构在数据量较小的时候会采用压缩列表的格式进行存储,因为 一个线性数组通常会被CPU的缓存更好的命中(线性数组有更好的局部性),从而提升了访问的速度。
一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist却是将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。它是一个表(list),但其实不是一个链表(linked list)。
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
- zlbytes:表示这个ziplist占用了多少空间,或者说占了多少字节,这其中包括了zlbytes本身占用的4个字节;
- zltail:表示到ziplist中最后一个元素的偏移量,有了这个值,pop操作的时间复杂度就是O(1)了,即不需要遍历整个ziplist;
- zllen:表示ziplist中有多少个entry,即保存了多少个元素。由于这个字段占用16个字节,所以最大值是216-1,也就意味着,如果entry的数量超过216-1时,需要遍历整个ziplist才知道entry的数量;
- entry:真正保存的数据,有它自己的编码;
- zlend:专门用来表示ziplist尾部的特殊字符,占用8个字节,值固定为255,即8个字节每一位都是1。
[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
| | | | | |
zlbytes zltail entries "2" "5" end
intset整数集合
是一个有序的整型数组。它在内存分配上与ziplist有些类似,是连续的一整块内存空间,而且对于大整数和小整数采取了不同的编码,尽量对内存的使用进行了优化。
skiplist跳表
skiplsit跳跃链表=有序链表+多级索引;
空间换时间。
链表 | 跳表 | |
---|---|---|
查询的时间复杂度 | O(n) | O(log(n)) |
查找、插入、删除以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度和跳表是一样的。但是使用跳表而不是红黑树的原因:
1)按照区间查找数据这个操作,红黑树的效率没有跳表高。跳表可以在 O(logn)时间复杂度定位区间的起点,然后在原始链表中顺序向后查询就可以了。
2)相比于红黑树,跳表还具有代码更容易实现、可读性好、不容易出错、更加灵活等优点。
3)插入、删除时跳表只需要调整少数几个节点,红黑树需要颜色重涂和旋转,开销较大。
typedef struct redisObject {
//具体的类型如String、hash、set、list、sortedset等。通过type命令进行查看,其实主要是约束api使用
unsigned type:4;
//更加深层次的类型,代表底层的优化。如int,embstr等。通过object encoding命令进行查看。
unsigned encoding:4;
//设置内存淘汰策略时使用 24byte
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
//用于垃圾回收的引用计数 4byte
int refcount;
//真实存储value的指针 8byte
void *ptr;
} robj;
String字符串
可以包含任何数据,value最大是512M。
SDS最长为什么是512MB?
因为len
是使用int修饰的,int最大数值换算一下就是512MB
Value | 应用场景 |
---|---|
字符串 | session共享 uuid数据库ID VFS in mem 虚拟文件系统;set /root/路径 值;get /root/路径;注:小文件 |
数值 | 限流器 统计 点击率 |
bitmap | |
图片 |
-------------------------- 增 ----------------------------------------------
#设置k-v
set key value
##设置k-v,如果key不存在则设置,如果key存在,则什么也不做。成功返回1,失败返回0
setnx key value
#批量设置k-v
MSET key1 "value1" key2 "value2" keyn "valuen"
#批量设置k-v。原子性操作:key都不存在则设置,存在一个key则失败
MSETNX key1 "value1" key2 "value2" keyn "valuen"
# 传统对象缓存 set 对象:id {属性:value,属性:value}
set user:1 {name:zhangsan,age:27}
# 可以用来缓存对象 mset 对象:id:属性 value 对象:id:属性 value
mset user:1:name zhangsan user:1:age 22
mget user:1:name user:1:age
---------------------------- 删 --------------------------------------------
##删除key,时间复杂度为O(1)
del key
---------------------------- 改 --------------------------------------------
#数字++
incr key
#数字--
decr key
#数字+=数值
incrby key 数值
#数字-=数值
decrby key 数值
#value追加字符串。如果当前key不存在,就相当于set key
append key "字符串"
#替换指定位置的字符
setrange key index "字符串"
#获取字符串value并设置为新字符串value。CAS的一种方案
GETSET key "new字符串"
------------------------------ 查 ------------------------------------------
##查询value
get key
#查询value的长度
strlen key
#字符串截取值【startIndex,endIndex】
getrange key startIndex endIndex
#返回所有指定的key的字符串value
MGET key1 key2 keyn
List列表
list 实际是一个链表,左右都可以插入值。根据放入顺序有序,redis不会去排序
Value | 应用场景 |
---|---|
栈(存取同向) | JVM 宕机数据消失,迁移到redis,服务无状态 |
队列(存取异向) | JVM 宕机数据消失,迁移到redis,服务无状态 |
数组 | JVM 宕机数据消失,迁移到redis,服务无状态 |
消息排队 | |
删除数据,保留热数据 | 分页,微信小红包 |
----------------------------- 增 -------------------------------------------
##将一个或多个值插入到列表头部
Lpush key value
##将一个或多个值插入到列表尾部
rpush key value
##在列表的value前before或者后after插入值
Linsert key before|after value 值
------------------------------- 删 时间复杂度为O(M) ---------------------------
##移除key中指定个数的value
lrem key 移除个数 value
##获取并移除列表头部
Lpop key
##获取并移除列表尾部
Rpop key
-------------------------------- 改 ----------------------------------------
##指定下标赋值
Lset key index 值
##key截取指定下标的元素
ltrim key startIndex endIndex
##移除列表最后一个元素并移动到新列表中
rpoplpush key new-key
---------------------------------- 查 --------------------------------------
##查询List中的值。endIndex=-1代表查询所有值
lrange key startIndex endIndex
##获取指定下标的值
Lindex key index
##列表长度
llen key
Hash哈希
类似java里的map<key,map<key,value>>
Value | 应用场景 |
---|---|
对象存储 | |
商品详情页 | |
聚合场景 |
----------------------------- 增 -------------------------------------------
##新增
hset key 字段名 value
##批量新增
hmset key 字段名1 value 字段名1 value
##设置k-v,如果key不存在则设置,如果key存在,则什么也不做。成功返回1,失败返回0
hsetnx key 字段名 value
##新增对象
set 对象:id:属性K v
get 对象:id:属性K
------------------------------- 删 时间复杂度为O(M) ------------------------------
##删除指定字段
hdel key 字段名
-------------------------------- 改 ----------------------------------------
#数字+=数值
HINCRBY K 属性 数值
---------------------------------- 查 --------------------------------------
##获取一个字段值
hget key 字段名
##获取多个字段值
hmget key 字段名1 字段名1
##获取所有值
hgetall key
##长度
hlen key
##判断key是否存在字段
hexists key 字段名
##获取所有字段
hkeys key
##获取所有value
HVALS key
渐进式rehash
每个字典都有两个hashtable的结构,它是为了实现渐进式的rehash。当数组的hash冲突很多时,即一个数组槽位产生了很长的链表。此时我们就需要扩容,redis也采用*2的扩容方式。当数据量很大时,一次性的将数据copy到新的数组时就会有一定的性能影响。redis为了提高性能,避免出现卡顿现象。并没有选择一次性完成扩容,而是采用了渐进式rehash的方式进行扩容。空间分配完成之后,逐个的遍历每个数组槽位,然后进行copy,直到旧的数组数据全部搬到新的数组为止。最后使用新的数组。
string和hash的选择?
每一个string的set都会占用dictht的一个槽位,当较多时容易造成dictht的rehash操作。hash的set一个对象的多个k-v只会占用一个hash key的槽位。但是hash对象内部的key是不可以设置过期时间的。
Set集合
特点:无序不重复
Set是一个特殊的value为空的Hash
应用场景 | |
---|---|
网页的UV | |
共同好友 | 交集 |
推荐好友 | 差集 |
----------------------------- 增 -------------------------------------------
##插入值
sadd key V1 V2 V3 V4 ...
------------------------------- 删 时间复杂度为O(M) ---------------------------------
##删除key中的指定value
srem key value
##随机获取并删除key中的value
spop key
##讲key1中的value移动到key2
smove key1 key2 value
-------------------------------- 改 ----------------------------------------
---------------------------------- 查 --------------------------------------
##查询所有值
Smembers key
##判断key是否存在
Sismember key
##key的value个数
Scard key
##随机查询key中的value
SRANDMEMBER K num
##K1 K2的并集
SUNION K1 K2
##K1 K2的交集。案例:共同好友
SINTER K1 K2
##以K1为主做差集。即k1中排除k2中有的元素。案例:推荐好友
SDIFF K1 K2
Zset有序集合
特点:有序不重复
链表:插入数据很快,只需要把节点的地址,查询很慢需要从头开始搜索,所以优化了链表,使用了skiplsit跳跃链表。
应用场景 | |
---|---|
排行榜 | 动态排序 OBJECT encoding K K的value的数据结构 |
评论(可以按点赞排序,时间排序等)分页 | 动态分页 |
权重 | 通过分值 |
------------------------------- 增 -----------------------------------------
##新增
zadd key 分值1 元素1 分值2 元素2 ...
##从小到大ZRANGE K 0 -1 withscores##从大到小ZrevRANGE K 0 1 withscores
-------------------------------- 删 时间复杂度为O(M) -----------------------------
##删除
zrem key 元素
--------------------------------- 改 ---------------------------------------
##
--------------------------------- 查 ---------------------------------------
##查询key中的值。endIndex=-1代表查询所有值
zrange key startIndex endIndex
##通过分值从小到大排序,只显示元素。min最小为-inf;max最大为+inf。
zrangeBYSCORE key min max
##通过分值从小到大排序,元素与分值都显示。min最小为-inf;max最大为+inf。
zrangeBYSCORE key min max withSCOREs
##通过分值从大到小排序,只显示元素。min最小为-inf;max最大为+inf。
zrevrangeBYSCORE key max min
##通过分值从大到小排序,元素与分值都显示。min最小为-inf;max最大为+inf。
zrevrangeBYSCORE key max min withSCOREs
##key的value个数
zcard key
##key中分值在【分值1,分值2】的value个数
zcount key 分值1 分值2
geo 地理位置
应用场景 | |
---|---|
附近的人 | |
微信朋友定位 | |
打车距离 |
------------------------------------------------------------------------
##添加位置
geoadd key 经度 纬度 省份/城市/街道名称
------------------------------ 查 ------------------------------------------
##查询经纬度
geopos key 省份/城市/街道名称
##查询经纬度返回长度11的字符串
geohash key 纬度 省份/城市/街道名称
##返回两个位置的距离
geodist key 名称1 名称2
##以给定的经纬度为圆心,找出半径内的key
georedius key 经度 纬度 半径 单位
georedius key 经度 纬度 半径 单位 经度withdist 纬度withcoord
georedius key 经度 纬度 半径 单位 查询数量count 数值
##以给定的key为圆心,找出半径内的key
georediusbymember key 半径 单位
hyperloglog基数统计
基数:不重复元素的个数
应用场景 | |
---|---|
网址的UV访问人数 | 一个人访问多次也算是一个人 可以使用Set保存用户id,因为Set是不允许重复的,所以统计Set中元素个数也可以统计UV访问人数,但是这种方式保存了大量的用户id,而目的只是做统计UV访问人数。 使用 HyperLogLog 最多需要 12k 就可以统计大量的用户数,并且空间复杂度为o1。 |
---------------------------- 增 --------------------------------------------
##添加元素
PFadd key v1 v2 ... vn
##key1 key2取并集添加到key中
PFmerge key key1 key2
----------------------------- 查 -------------------------------------------
##查询key中value个数
PFcount key
bitmap位图
二进制位存储。**可以简单的认为就是个数组,里面的内容为0或1(二进制位数组)。**借助 SDS 实现
应用场景 | |
---|---|
任意时间窗统计用户登录天数 | 46个字节存储365天打卡记录 |
权限 | |
2亿用户中查找任意时间窗活跃用户并送礼物,要准备多少件礼物 |
---------------------------- 增 --------------------------------------------
##添加元素
setbit key 偏移量offset 二进制value
----------------------------- 查 -------------------------------------------
##查询key中offset的二进制value
getbit key offset
##统计key中个数
bitcount key startOffset endOffset
##二进制与
bitop and k k1 k2
##二进制或
bitop or k k1 k2
事务transactions
1)Redis不保证原子性。Redis单条命令原子性执行,但事务不保证原子性。
事务中任意命令执行失败-语法错误,其余的命令都不会被执行。
事务中任意命令执行失败-语法无误,代码逻辑错误,其余的命令仍会被执行。
2)Redis事务无法回滚。因为Redis没有行锁。
3)Redis事务没有隔离级别。批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!
事务命令 | 用途 |
---|---|
MULTI | 标记一个事务块的开始。 |
WATCH key | 在事务MULTI后,EXEC前,监视一个或多个key,如果被监视的key被其他命令改动,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。( 类似乐观锁 ) |
UNWATCH | 取消 WATCH 命令对所有 key 的监视。 |
EXEC | 执行所有事务块内的命令。 |
DISCARD | 取消事务,放弃执行事务块内的所有命令。 |
事务流程
将多个命令放入一个队列中。一次性、顺序性、排他性执行。
开始事务 MULTI
监视 WATCH
在事务MULTI后,EXEC前,监视一个或多个key,如果被监视的key被其他命令改动,事务队列将不会被执行,同时返回nil以通知调用者事务执行失败。
WATCH可以实现乐观锁。
悲观锁:认为什么时候都会出现问题,无论做什么都加锁。
乐观锁:认为什么时候都不会出现问题,更新数据的时候去判断数据是否有变更。
命令入队
单条命令按顺序入队列中缓存
执行事务 EXEC / 放弃事务 DISCARD
收到 EXEC 命令后进入事务执行,其他客户端提交的命令请求不会插入到事务执行命令序列中。
取消监视 UNWATCH
取消 WATCH 命令对所有 key 的监视。
Redis.conf
NETWORK网络配置
# 指定redis只接收哪些IP的请求,如果配置bind 127.0.0.1则只监听本机的客户端请求,(可配置多个ip)
# 如果注释掉不进行设置,则代表接收所有IP的请求。
bind 127.0.0.1
#是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只能本地进行访问,
#拒绝外部访问。如果开启了密码和bind,可以开启。否则最好关闭,即设置为no。
protected-mode yes # 开启此模式后,需要auth
# 设置监听的端口号,默认是6379。如果设置0,则不监听任何tcp请求。
port 6379
#在高并发环境下,并且客户端连接请求慢时,你需要设置更高的值。此值代表了TCP连接中已完成队列
#(完成三次握手之后)的长度,此值必须会受到Linux系统下/proc/sys/net/core/somaxconn文件
# 中值的限制,redis默认是511,而Linux的默参数值是128。所以我们得同时提高这2个值的大小,
#以至于能获得更好的性能以及预期的效果。一般会将它修改为2048或者更大。
# 在/etc/sysctl.conf中添加:net.core.somaxconn = 2048,然后在终端中执行sysctl -p
tcp-backlog 511
# 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0
timeout 0
# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
# equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 300 seconds, which is the new
# Redis default starting with Redis 3.2.1.
tcp-keepalive 300
GENERAL通用
# 默认情况下,Redis 是否作为守护进程运行。需要开启的话,改为 yes
daemonize yes
# 可通过 upstart 和 systemd 管理 Redis 守护进程
supervised no
# 以后台进程方式运行 redis,则需要指定 pid 文件
pidfile /var/run/redis_6379.pid
# 日志级别。可选项有:
# debug(记录大量日志信息,适用于开发、测试阶段);
# verbose(较多日志信息);
# notice(适量日志信息,使用于生产环境);
# warning(仅有部分重要、关键信息才会被记录)。
loglevel notice
# 日志文件的位置,当指定为空字符串时,为标准输出,如果redis为守护进程模式运行,那么日志将会输出到 /dev/null
logfile ""
# 是否把日志记录到系统日志
syslog-enabled no
# 设置系统日志的id
syslog-ident redis
# 指定syslog设备,必须是user或则local0到local7
syslog-facility local0
# 默认有16个数据库,可通过select切换数据库 (编号为[0,15])。默认的数据库是 0
databases 16
# 是否总是显示 logo
always-show-logo yes
REPLICATION 主从复制
# 设置本机为slave服务,并且填写主节点的ip和端口号
replicaof 127.0.0.1 6379
# master的密码。如果master服务设置了密码保护,slave服务连接master时需要密码。
masterauth 123456
# 当一个slave与master失去联系时,或者复制正在进行的时候,slave应对请求的2种行为:
# 1:如果设置yes(默认值),slave 仍然会应答客户端请求,但返回的数据可能是过时,或者数据可能是空的在第一次同步的时候;
# 2:如果设置no,在你执行除了info和replicaOF和AUTH..等之外的其他命令时,slave返回一个"SYNC with master in progress"的错误。
replica-serve-stale-data yes
# 设置slave是否是只读的。从2.6版起,slave默认是只读的。
replica-read-only yes
# 主从数据复制是否使用无硬盘复制功能。
repl-diskless-sync no
# 当五磁盘复制被开启时,等待5s后再开始复制,因为要等更多replica重新连接过来,可以公用一个RDB。如果不延迟等待可以设置为0
repl-diskless-sync-delay 5
# 指定slave定期ping master的周期,默认10秒钟。
# repl-ping-replica-period 10
# 设置主库批量数据传输时间或者ping回复时间间隔,默认值是60秒 。
# master node将rdb快照文件发送给replica node,如果rdb复制时间超过60秒,那么replica node就会认为复制失败,
# 可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
# repl-timeout 60
# 指定向slave同步数据时,是否禁用socket的NO_DELAY选项。若配置为“yes”,则禁用NO_DELAY,则TCP协议栈会合并小包统一发送,
# 这样可以减少主从节点间的包数量并节省带宽,但会增加数据同步到slave的时间。若配置为“no”,表明启用NO_DELAY,
# 则TCP协议栈不会延迟小包的发送时机,这样数据同步的延时会减少,但需要更大的带宽。则TCP协议栈不会延迟小包的发送时机,
# 这样数据同步的延时会减少,但需要更大的带宽。 通常情况下,应该配置为no以降低同步延时,
# 但在主从节点间网络负载已经很高的情况下,可以配置为yes。
repl-disable-tcp-nodelay no
# 设置主从复制backlog容量大小。这个 backlog 是一个用来在 slaves 被断开连接时存放 slave 数据的 buffer,
# 所以当一个 slave 想要重新连接,通常不希望全部重新同步,只是部分同步就够了,
# 仅仅传递 slave 在断开连接时丢失的这部分数据。这个值越大,salve 可以断开连接的时间就越长。
# repl-backlog-size 1mb
# A value of 0 means to never release the backlog.
# 配置当master和slave失去联系多少秒之后,清空backlog释放空间。当配置成0时,表示永远不清空。
# repl-backlog-ttl 3600
# 当master不能正常工作的时候,Redis Sentinel会从slaves中选出一个新的 master,这个值越小,
# 就越会被优先选中,但是如果是0,那是意味着这个slave不可能被选中。默认优先级为 100。
replica-priority 100
# redis提供了可以让master停止写入的方式,如果配置了min-replicas-to-write,健康的slave的个数小于N,mater就禁止写入。
# master最少得有N个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,
# 但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。
# 延迟小于min-replicas-max-lag秒的slave才认为是健康的slave
# min-replicas-to-write 3
# min-replicas-max-lag 10
SECURITY安全
# redis连接密码
requirepass 123456
# 将命令重命名。为了安全考虑,可以将某些重要的、危险的命令重命名。当你把某个命令重命名成空字符串的时候就等于取消了这个命令。rename-command CONFIG b4dw78w47d15w4d8w,表示将CONFIG命令重命名为b4dw78w47d15w4d8w;rename-command CONFIG "" 代表禁用CONFIG
rename-command CONFIG ""
CLIENTS 客户端
# 设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数-32(redis server自身会使用一些),
# 如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息。
#
# maxclients 10000
MEMORY内存管理
# 指定Redis最大内存容量
# Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区,格式:maxmemory <bytes> 。
maxmemory <bytes>
# maxmemory-policy 内存达到上限的清除策略:
# volatile-lru:利用 LRU 算法移除设置过过期时间的 key。
# allkeys-lru:利用 LRU 算法移除任何 key。
# volatile-lfu:利用LFU算法移除设置了过期时间的key
# allkeys-lfu:利用LFU算法移除任何key
# volatile-random:随机移除设置过过期时间的 key。
# volatile-ttl:移除即将过期的 key,根据最近过期时间来删除(辅以 TTL)
# allkeys-random:随机移除 key。
# noeviction:不移除任何 key,只是返回一个写错误。默认选项
maxmemory-policy noeviction
# redis默认选择5个样本进行检测,可以通过maxmemory-samples进行设置样本数。
maxmemory-samples 5
# 是否开启salve的最大内存
replica-ignore-maxmemory yes
# 当使用DEL命令时,会停止执行新的命令,删除key对应的value是个小对象,速度会很快,如果是个大对象,则会阻塞比较长时间。
# 是否以非阻塞方式释放内存
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
内存淘汰策略
当Redis已用内存达到maxmemory上限时,会触发相应的溢出控制策略主动删除key。
key没有过期时间,但是被删除掉了?
1)内存淘汰策略为allkeys-random,allkey-lru,allkeys-lfu三种中的某一种的时候,没设置过期时间的key也会被删除掉。
maxmemory-policy: | 不处理 |
---|---|
noeviction(默认) | 不移除任何key,拒绝所有写操作并返回oom错误 ,此时只响应读操作。 |
maxmemory-policy: | 过期key (设置了过期时间的) |
---|---|
volatile-ttl | 根据过期时间的先后移除key |
volatile-random | 随机移除设置过过期时间的key |
volatile-lru (推荐使用) | 利用LRU算法(最近最少使用Least Recently Used)删除过期时间的key |
volatile-lfu | 利用LFU算法(最不经常使用 Least Frequently Used )删除过期时间的key |
maxmemory-policy: | 所有key(包括设置过期时间和不设置过期时间的) |
---|---|
allkeys-random | 无差别的随机移除 |
allkey-lru | 利用LRU算法(最近最少使用Least Recently Used)移除任何key。 |
allkeys-lfu | 利用LFU算法(最不经常使用 Least Frequently Used )移除任何key。 |
LRU | LFU |
---|---|
Least Recently Used | Least Frequently Used |
最近最少使用 | 最不经常使用 |
以访问时间为参考 | 以访问次数为参考 |
绝大多数情况下我们都使用LRU算法,当存在大量热点缓存数据的时候才使用LFU。
SNAPSHOPTING快照rdb
# 保存数据到磁盘。格式是:save <seconds> <changes> ,即在seconds秒之后至少有changes个keys发生改变则保存一次。如果不想保存数据到磁盘,则注释掉所有save行即可,也可改为save ""
# 900秒(15分钟)内至少 1 个 key 值改变(则进行数据库保存--持久化)
save 900 1
# 300秒(5分钟)内至少 10 个 key 值改变(则进行数据库保存--持久化)
save 300 10
# 60秒(1分钟)内至少 10000 个 key 值改变(则进行数据库保存--持久化)
save 60 10000
# 持久化出现错误后,是否依然进行继续进行工作
# 默认情况下,如果redis最后一次的后台保存失败,redis将停止接受写操作,这样以一种强硬的方式让用户知道数据不能正确的持久化到磁盘,否则就会没人注意到灾难的发生。如果后台保存进程重新启动工作了,redis也将自动的允许写操作。如果你要是安装了靠谱的监控,你可能不希望redis这样做,那你就改成no。
stop-writes-on-bgsave-error yes
# 使用压缩 rdb 文件 yes:压缩,但是需要一些 cpu 的消耗;no:不压缩,需要更多的磁盘空间
rdbcompression yes
# 是否校验 rdb 文件,更有利于文件的容错性,但是在保存 rdb 文件的时候,会有大概 10% 的性能损耗
rdbchecksum yes
# rdb 文件名称
dbfilename dump.rdb
# 数据库存放目录。必须是一个目录,aof文件也会保存到该目录下。默认在启动脚本相同的目录下。
dir ./
APPEND ONLY MODE追加模式aof
# 是否启用aof持久化方式 。即是否在每次更新操作后进行日志记录,默认配置是no,采用异步方式把数据写入到磁盘,如果不开启,可能会在断电时导致部分数据丢失。
# AOF 和 RDB 可以同时开启。如果AOF开启了,恢复时会选用AOF
appendonly no
# AOF 文件名称
appendfilename "appendonly.aof"
# appendfsync aof 持久化策略的配置:
# no:不执行 fsync,由操作系统保证数据同步到磁盘,速度最快但安全性最差。
# always:每次写入都执行 fsync,非常慢但也非常安全。
# everysec:每秒执行一次 fsync,可能会导致丢失这1s数据。推荐使用
appendfsync everysec
# 指定是否在后台aof文件rewrite期间调用fsync,默认为no,表示要调用fsync(无论后台是否有子进程在刷盘)。 Redis在后台写RDB文件或重写AOF文件期间会存在大量磁盘IO,此时,在某些linux系统中,调用fsync可能会阻塞。
no-appendfsync-on-rewrite no
# 当AOF文件增长到一定大小的时候Redis能够调用BGREWRITEAOF对日志文件进行重写。当AOF文件大小的增长率大于该配置项时自动开启重写。
auto-aof-rewrite-percentage 100
# 当AOF文件增长到一定大小的时候Redis能够调用BGREWRITEAOF对日志文件进行重写 。当AOF文件大小大于该配置项时自动开启重写。
auto-aof-rewrite-min-size 64mb
# redis在启动时可以加载被截断的AOF文件,而不需要先执行redis-check-aof工具。
aof-load-truncated yes
# 加载redis时,可以识别AOF文件以“redis”开头字符串并加载带前缀的RDB文件,然后继续加载AOF尾巴
aof-use-rdb-preamble yes
appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这 种方式在许多应用中已经足够用了 appendfilename "appendonly.aof" # appendfilename AOF 文件名称 appendfsync everysec # appendfsync aof持久化策略的配置 # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 # always表示每次写入都执行fsync,以保证数据同步到磁盘。 # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。 No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安 全性Auto-aof-rewrite-min-size # 设置重写的基准值 Auto-aof-rewrite-percentage #设置重写的基准值
REDIS CLUSTER 集群
# 集群开关,默认是不开启集群模式
cluster-enabled yes
# 集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动
# 配置,这个配置文件由Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系
# 统中配置文件名称不冲突
cluster-config-file nodes-6379.conf
# 节点互连超时的阀值。集群节点超时毫秒数
#
cluster-node-timeout 15000
cluster-replica-validity-factor 10
cluster-migration-barrier 1
cluster-require-full-coverage yes
cluster-replica-no-failover no
cluster-allow-reads-when-down no
CLUSTER DOCKER/NAT support
# In certain deployments, Redis Cluster nodes address discovery fails, because
# addresses are NAT-ted or because ports are forwarded (the typical case is
# Docker and other containers).
#
# In order to make Redis Cluster working in such environments, a static
# configuration where each node knows its public address is needed. The
# following two options are used for this scope, and are:
#
# * cluster-announce-ip
# * cluster-announce-port
# * cluster-announce-bus-port
#
# Each instruct the node about its address, client port, and cluster message
# bus port. The information is then published in the header of the bus packets
# so that other nodes will be able to correctly map the address of the node
# publishing the information.
#
# If the above options are not used, the normal Redis Cluster auto-detection
# will be used instead.
#
# Note that when remapped, the bus port may not be at the fixed offset of
# clients port + 10000, so you can specify any port and bus-port depending
# on how they get remapped. If the bus-port is not set, a fixed offset of
# 10000 will be used as usually.
#
# Example:
#
# cluster-announce-ip 10.1.1.5
# cluster-announce-port 6379
# cluster-announce-bus-port 6380
持久化persistence
Redis 是内存数据库,而内存的特性是掉电丢失数据。所以 Redis 提供了持久化功能!
RDB快照
RDB:Redis DataBase
文件:dump.rdb
特点:恢复速度快,可能丢失最后一次持久化后的数据。redis默认
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化的数据可能丢失。
优点
- rdb是一个非常紧凑的文件,占用磁盘空间小
- 恢复速度快
- 每次执行rdb操作时,只需耗费一个fork子进程(fork操作当数据量很大时也会造成一定的影响)的操作,不怎么影响主进程的性能
缺点
- 由于是基于时间间隔的条件执行,所以可能导致时间间隔的数据丢失。
- 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大时,fork的过程是比较耗时的。可能导致redis响应性能下降
AOF日志
AOF:Append Only File
文件:appendonly.aof (如果文件有错误,redis是启动不了的,可以用redis-check-aof工具修复。redis-check-aof --fix appendonly.aof)
特点:将Redis执行过的所有的写指令记录下来,追加到日志文件中,redis重启就根据日志文件的写指令从头到尾执行一次,完成数据的恢复工作 。
Redis多久才将数据fsync同步到AOF文件一次 | 速度 | 数据安全 | |
---|---|---|---|
appendfsync always | 每次发生数据变更会被立即记录到磁盘 | 慢 | 数据完整 |
appendfsync everysec | 每秒同步到磁盘 | 较快 | 如果宕机有一秒的数据丢失 |
appendfsync no | 从不同步到磁盘 | 更快 | 不安全 |
优点
- 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
- AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
- Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
- AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
缺点
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
- 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
重写机制
AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值(默认64m)时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令 bgrewriteaof !
重写原理:
AOF 文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,这点和快照有点类似!
重写触发机制:
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的已被且文件大于64M的触发。
把RDB快照与AOF日志混合使用
RDB:整点存储。比如在从机上15分钟备份一次
AOF:整点后的数据增量修复
Redis高可用HA架构
主从架构Master-Slave
- 配置从机 replicaof
- 只能提供简单的备份。主机宕机后,因为没有哨兵,所以不能自动选举主机。
- 基于rbd快照去做异步复制
- 主从复制风暴,多个从节点从主节点复制数据。只要重新连接Master就会执行一次全量复制。
主从复制原理:
- 从服务器连接主服务器,发送SYNC命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;(从服务器初始化完成)
- 主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)
主从复制优缺点:
优点:
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离
- 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成
- Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。
- Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。
- Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据
缺点:
- Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
哨兵模式Sentinel
优点:
- 哨兵模式基于主从复制模式,所有主从的优点哨兵模式都具有,即主从的优化。
- 哨兵是独立的进程。每个哨兵都是一个redis实例。一般要3个及以上奇数个的哨兵
- 当半数以上的哨兵发现主节点宕机后,通过投票算法在从节点中自动选举出主机节点。
- 只有高可用的功能,但是没有数据分片的功能(每台redis服务器都存储相同的数据)。
缺点:
- 只有一个主节点对外提供服务,redis单机支持的并发数最大只能到10万,并且单节点的内存大小一般设置为10G(防止持久化文件过大影响数据的恢复或主从复制)。
- 可能会有脑裂问题从而丢失数据。旧主节点还没跟从节点同步数据,就出现宕机或者网络故障,此时哨兵自动选举主节点后,旧主节点再连接进来,会自动转换为从节点,根据redis主从同步机制,旧主节点会向新主节点申请全量数据,新主节点bgsave rdb,回传全量的rdb给旧主节点,此时旧主节点首先会flushdb清空所有数据再加载全量的rdb,此时旧主节点还同步的数据就会丢失。 解决方案:Redis.conf中配置主节点可用性跟从节点可接入。min-slaves-to-write(主节点能进行数据同步的最小从节点数量)和min-slaves-max-log(主从节点间进行数据复制时,从节点给主节点发送ack消息的最大延时)。
- 自动选举并切换主机节点的时候,写操作对外不可用。
1)配置哨兵 sentinel.conf
sentinel monitor
# 哨兵运行的端口 默认26379
port 26379
# 哨兵工作目录
dir /tmp
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# sentinel monitor 主节点名字 ip port 多少个哨兵判断这个节点才算死亡
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass <master-name> <password>
# 当在 Redis 实例中开启了 requirepass foobared 授权密码 这样所有连接 Redis 实例的客户端都要提供密码
# 设置哨兵 sentinel 连接主从的密码,注意必须为主从设置一样的验证密码
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# sentinel down-after-milliseconds <master-name> <milliseconds>
# 指定多少毫秒之后,主节点没有应答哨兵 sentinel,此时,哨兵主观上认为主节点下线,默认 30 秒
sentinel down-after-milliseconds mymaster 30000
# sentinel parallel-syncs <master-name> <numslaves>
# 这个配置项指定了在发生 failover 主备切换时最多可以有多少个 slave 同时对新的 master 进行同步
# 这个数字越小,完成 failover 所需的时间就越长
# 但是如果这个数字越大,就意味着越多的 slave 因为 replication 而不可用
# 可以通过将这个值设为 1 来保证每次只有一个 slave 处于不能处理命令请求的状态
sentinel parallel-syncs mymaster 1
# sentinel failover-timeout <master-name> <milliseconds>
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
# 1. 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间
# 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到 slave 被纠正为向正确的 master 那里同步数据时。
# 3. 当想要取消一个正在进行的 failover 所需要的时间。
# 4. 当进行 failover 时,配置所有 slaves 指向新的 master 所需的最大时间。
# 不过,即使过了这个超时,slaves 依然会被正确配置为指向 master,但是就不按 parallel-syncs 所配置的规则来了
# 默认三分钟
sentinel failover-timeout mymaster 180000
# sentinel notification-script <master-name> <script-path>
# SCRIPTS EXECUTION
# 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回 1,那么该脚本稍后将会被再次执行,重复次数目前默认为 10
# 若脚本执行后返回 2,或者比 2 更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为 1 时的行为相同。
# 一个脚本的最大执行时间为 60s,如果超过这个时间,脚本将会被一个 SIGKILL 信号终止,之后重新执行。
# 通知型脚本:当 sentinel 有任何警告级别的事件发生时(比如说 redis 实例的主观失效和客观失效等),将会去调用这个脚本
# 这时这个脚本应该通过邮件,SMS 等方式去通知系统管理员关于系统不正常运行的信息。
# 调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。
# 如果 sentinel.conf 配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则 sentinel 无法正常启动成功。
# 通知脚本
sentinel notification-script mymaster /var/redis/notify.sh
# sentinel client-reconfig-script <master-name> <script-path>
# 客户端重新配置主节点参数脚本
# 当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,通知相关的客户端关于 master 地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前 <state> 总是 “failover”,<role> 是 “leader” 或者 “observer” 中的一个。
# 参数 from-ip,from-port,to-ip,to-port是用来和旧的 master 和新的 master (即旧的 slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
2)启动哨兵 redis-sentinel
集群模式cluster
由多个主从节点组成分布式集群。
节点 = slot插槽[0,16383] + cluster集群管理插件
对槽位进行分片,[0,5000] [5000,10000] [10000,16383]
发起一次存取key的请求,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值去找到对应的插槽所对应的节点,然后跳转到这个节点上进行存取操作。
客户端本地会存储一份服务器的槽位映射表,如果错误的话,服务端会返回moved错误重定向,客户端进行重新访问并更新本地映射表。
Redis-Cluster采用无中心结构,它的特点如下:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的不可用判断是通过集群中超过半数的节点检测失效时才生效。
- 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
- 16384个槽位进行分片,2的14次方用bitmap表示需要2k。(为啥不用16次方即8k的槽位,因为集群中的节点需要互相通信,各个节点会把自己负责的槽位信息用2k大小的bitmap发送给其他节点,2k相对于8k在满足期望的条件下减少了网络通信的压力)
- 客户端使用CRC16算法定位到指定的槽位
- 客户端本地会存储一份服务器的槽位映射表,如果错误的话,服务端会返回moved错误重定向,客户端进行重新访问并更新本地映射表。
- min-replicas-to-write 3 至少有多少节点写入成功,才返回给客户端成功,可有效防止脑裂问题
- cluster-require-full-coverage yes 集群是否完整才能对外提供服务,为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用。如果为yes则表示集群不可用。
- 集群批量操作,例如mset key1 val1 key2 val2。redis集群默认只支持所有的key落在同一个slot的情况,如果有多个key一定要使用mset命令在集群上操作,则可以在key的前面加上{xx},这样参数数据分片hash计算的只会是大括号里面的值,这样能确保不同的key落在同一个slot里面去,示例:mset {user1}:name xx {user1}:age 18
Redis三大框架
框架 | 概念 | 优点 | 可伸缩 |
---|---|---|---|
Jedis | 是Redis的Java实现客户端,提供了比较全面的Redis命令的支持 | 比较全面的提供了Redis的操作特性 | 使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis |
Redisson | 实现了分布式和可扩展的Java数据结构 | 促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列 | 基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作 |
Lettuce | 高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器 | 主要在一些分布式缓存框架上使用比较多 | 基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作 |
网址
Jedis api 在线网址:http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html
redisson 官网地址:https://redisson.org/
redisson git项目地址:https://github.com/redisson/redisson
lettuce 官网地址:https://lettuce.io/
lettuce git项目地址:https://github.com/lettuce-io/lettuce-core
Jedis
<!-- jedis依赖包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
Jedis采用直连,多个线程操作是不安全的,如果想要避免安全性的问题,需要使用JedisPool 连接池(BIO)。
Jedis 连接池实现redis链接对象
Jedis方法 | 作用 |
---|---|
llen | 返回列表的长度 |
pipelined | 在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应,获取pl对象 |
isConnected | 当前socket是否关闭,关闭返回true |
close | 回收js资源 |
JedisPool 连接池管理redis链接对象
JedisPool方法 | 作用 |
---|---|
getResource | 获取连接池的一个js对象 |
returnResource(js) | 处理链接异常 |
maxActive | 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。 |
JedisPoolConfig
JedisPoolConfig方法 | 作用 |
---|---|
setBlockWhenExhausted | 连接超时时是否阻塞,false时报异常,ture阻塞直到超时, 默认true |
setMaxIdle | 最大空闲连接数 |
setMaxTotal | 最大连接数 |
setMaxWaitMillis | 超时时间 |
setTestOnBorrow | 对拿到的connection进行validateObject校验 |
setTestOnReturn | 在进行returnObject对返回的connection进行validateObject校验 |
setTestWhileIdle | 定时对线程池中空闲的链接进行validateObject校验 |
Pipeline 管道流水线
Pipeline方法 | 作用 |
---|---|
rpoplpush | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。 |
rpop | 移除列表的最后一个元素,返回值为移除的元素。 |
syncAndReturnAll | 同步并获取信息 |
Redission
集群模式下多个Reids客户端请求应用,此时应用部署在多台机器上(同一个应用跑在不同的tomcat),此时nginx会负载均衡把请求分发到不同的机器上去,因为不是在一个jvm中,synchronized 等锁机制会失效。只能使用分布式锁去解决。
Reids 实现分布式锁有两种方式。
方式一:setNX加锁 + ex过期时间释放锁。
加锁: setNX lock:id value ex 过期时间
如果lock:id这个key不存在,则设置value(value=clientID=uuid),并且指定过期时间 。
释放锁: 业务代码 finaly{ if(clientID = key中的value) 才去del key }
此时还需要进行锁续命才能完善:开启定时子线程,定时检查业务代码有没有结束,如果还没结束,则把过期时间复原成初始值。
public String setNX() {
String lockKey = "lock:ID12345";
String clientId = UUID.randomUUID().toString();
// setNX lock:id clientId ex 过期时间
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (result) {
return "key存在,代表锁还未被释放";
}
try {
// 业务代码
}
finally {
if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
// 释放锁
redisTemplate.delete(lockKey);
// 此处还需要进行锁续命才是完善的。
// 开启定时子线程,定时检查业务代码有没有结束,如果还没结束,则把过期时间复原成初始值。
}
}
return "SUCCESS";
}
方式二:Redisson框架。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public Redisson redisson() {
// 单机
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
@Autowired
private Redisson redisson;
public String redissonLock() {
String lockKey = "lock:ID12345";
RLock redissonLock = redisson.getLock(lockKey);
// 加分布式锁
redissonLock.lock();
try {
// 业务代码
}
finally {
// 释放锁
redissonLock.unlock();
}
return "SUCCESS";
}
分布式锁原理
源码
public class RedissonLock extends RedissonExpirable implements RLock {
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
// 执行LUA脚本加锁
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.addListener(new FutureListener<Long>() {
public void operationComplete(Future<Long> future) throws Exception {
if (future.isSuccess()) {
Long ttlRemaining = (Long)future.getNow();
if (ttlRemaining == null) {
// 锁续命。
// 再主线程启动 过期时间/3 s后,开启future子线程。定时检查业务代码有没有结束,如果还没结束,则把过期时间复原成初始值。
scheduleExpirationRenewal(threadId);
}
}
}
});
return ttlRemainingFuture;
}
}
// 执行LUA脚本加锁
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}
// 锁续命
private void scheduleExpirationRenewal(final long threadId) {
if (!expirationRenewalMap.containsKey(this.getEntryName())) {
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
// 执行LUA脚本进行锁续命
RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});
future.addListener(new FutureListener<Boolean>() {
public void operationComplete(Future<Boolean> future) throws Exception {
RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
if (!future.isSuccess()) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());
} else {
if ((Boolean)future.getNow()) {
RedissonLock.this.scheduleExpirationRenewal(threadId);
}
}
}
});
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {
task.cancel();
}
}
}
}
LUA脚本。
-- 加锁逻辑
-- 如果lockKey不存在
if (redis.call('exists', KEYS[1]) == 0) then
-- LUA脚本可以保证原子性。不管LUA脚本有多少个redis命令,在redis中都可以当作一条命令执行。
-- hset lockKey clientId
redis.call('hset', KEYS[1], ARGV[2], 1);
-- 设置lockKey的过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
-- KEYS[1] = lockKey,
-- ARGV[1] = threadId = uuid,类似于clientId
-- ARGV[2] = internalLockLeaseTime, 类似过期时间
-- 锁重入逻辑
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
-- 锁续命
-- hexists lockKey clientId
-- 如果lockKey存在
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
-- 重新设置lockKey的过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end;
return 0;
锁优化
- 减小锁的粒度
- 分段锁,根据不同的业务设置不同的key,减少锁的竞争,提高性能。
- 尽量给key设置过期时间,如果再次操作则进行过期时间的续约
- 冷热分离
- 如果是读多写少的场景,那么可以采用分布式的读写锁来进行具体优化(redisson的读写锁也是用luna脚本实现,就是加了一个mode属性来区分读写操作)
- 多级缓存架构,加一层jvm的内存缓存
- 避免大value的key的操作,删除bigkey的尽量用scan循环删除
- 网络带宽拥塞
Lettuce
底层采用 Netty(NIO),实例可以在多个线程中进行共享。
不存在线程不安全的情况,可以减少线程数量。
SpringBoot整合redis
Spring boot2.x之前redis底层使用Jedis,Spring boot2.x之后redis底层使用Lettuce。
<!-- 操作redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 127.0.0.1
port: 6379
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms
RedisAutoConfiguration自动配置类
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(name = {"redisTemplate"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认的RedisTemplate没有过多的设置,redis对象都需要序列化的。
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
RedisProperties配置文件类
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
public RedisProperties() {
}
}
读写策略
策略名称 | 读取速度 | 写入速度 | 内存占用 | 一致性保证 | 适用场景 |
---|---|---|---|---|---|
Cache Aside旁路 | 快 | 快 | 高 | 不保证 | 读多写少的场景,数据较为静态,对一致性要求较低的应用 |
Read/Write Through 读写穿透 | 快 | 慢 | 低 | 保证一致性 | 高度一致性要求的应用,读写频率相对均衡,对数据准确性要求较高 |
Write Behind Through 异步缓存写入 | 快 | 快 | 高 | 不保证 | 写频率较高的应用,对读取一致性要求较高,可以接受一定程度的数据不一致性 |
Cache Coherency缓存一致性 | 快 | 快 | 高 | 保证一致性 | 多个缓存之间需要保持数据一致性的应用,如分布式系统等 |
Cache Aside旁路
Write Behind Through 异步缓存写入
MQ异步刷盘
Read/Write Through 读写穿透
Cache Coherency缓存一致性
缓存问题
缓存穿透
缓存和数据库中都没有的key对应的数据,进行高并发查询。
产生场景
1:高并发场景
2:攻击
解决方案
1 :key-null + 过期时间
对不存在的key设置一个特殊值,如key-null,同时缓存有效时间设置短一点,如30秒,这样子可以防止用同一个ID暴力攻击。
2:布隆过滤器BloomFilter
将全量数据放到布隆过滤器里,先在布隆过滤器里查一下这个key存不存在,如果布隆过滤器都没有,就不用去查数据库了。
布隆过滤器实际上是一个二进制数组,存在就是1,不存在就是0。
插入值:把值通过hash计算出存放的下标。数组下标位置存放1。
查询值:把值通过hash计算出存放的下标,去数组下标查询出结果,都为1就是存在。但是会存在误判,有不一定是真有,没有就一定是没有。
为了避免增删值的时候,hash碰撞的误判问题,可以把值进行多次hash计算。 hash计算的次数[0,255),hash计算次数越多得到的下标越多,准确性就越高,但是数组所占用的内存也就越大。
缺点:
1)布隆过滤器很难做删除。
2)误判。有不一定是真有,没有就一定是没有。
谷歌实现
// 预计要插入多少数据
int size = 10000;
//期望的误判率
double fpp = 0.01;
BloomFilter<Integer> bloomFilter = new BloomFilter(Funnels.integerFunnel(),size,fpp);
//新增数据
bloomFilter.put(key);
//判断是否存在
bloomFilter.mightContain(key);
redis实现
// 预计要插入多少数据
Long size = 10000L;
//期望的误判率
double fpp = 0.01;
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("List");
//初始化布隆过滤器
bloomFilter.tryInit(size, fpp);
//新增
bloomFilter.add(key);
// 判断是否在
bloomFilter.contains(key);
缓存击穿
热点key过期后持续的大并发穿破缓存,直接请求数据库。
产生场景
1:一般是缓存时间到期
解决方案
1:热点数据设置为永不过期
2:互斥锁,使用setnx分布式锁, 每个key同时只有一个线程去查询数据库,其他的请求进行CAS自旋。
缓存雪崩
在某一个时间段,缓存集中过期失效或者redis服务器宕机,很多不同key的请求会直接落到数据库上。
产生场景
1:缓存服务器重启
2:大量缓存集中在某一个时间段失效
解决方案
1:使用集群实现redis高可用。
2:限流降级。
3:缓存预热。把热点数据存入缓存中去再启动应用系统。
4:在高并发之前,手动触发去加载key,并设置不同的过期时间,让缓存失效的时间点尽量均匀。
数据库与缓存数据一致性
一致性问题
1)强一致性:加锁做互斥并且写数据库和写缓存搞成一个事务
2)最终一致性:有以下方案
并发读:无影响
并发写:存在数据库与缓存数据不一致性问题
并发读写:存在数据库与缓存数据不一致性问题
先写数据库,再删除缓存。出现数据库与缓存不一致的情形需要同时满足两个条件。
- 缓存刚好自动失效。
- 耗时:更新缓存>写数据库+删缓存。
绝大多数情况下,写数据请求比读数据情况耗时更长。系统同时满足上述两个条件的概率非常小。
问题:
1)缓存穿透。发送读写操作,每次都去数据库查询。
2)延时删除时间很难确定。
延时双删
阿里canal监听mysql的binlog日志+MQ
MQ消息中间件订阅mysql的binlog日志。mysql数据一旦被操作,binlog就会发送消息给MQ
redis获取到MQ的消息,解析binlog数据。
大key
定义:指缓存中存储的某个key的数据量过大。
对于 String 类型的 Value 值,值超过 10MB(数据值太大)。
对于 set 类型的 Value 值,含有的成员数量为 10000个(成员数量多)。
对于 List 类型的 Value 值,含有的成员数量为 10000个(成员数量多)。
对于 Hash 格式的 Value 值,含有的成员数量 1000 个,但所有成员变量的总 value 值大小为 1000MB(成员总的体积过大)。
影响:緩存性能下降。
- 单个请求占用10和带宽影响缓存中间件整体吞吐量
- 内存使用不均匀
- 网络拥塞
方案:
- 可以将大key拆分成多个小key进行存储
- 数据压缩
- 采用多级缓存,在应用内加本地缓存。
热key
过期key淘汰策略
key到期了,但是没有被删除掉?
1)key设置过期时间,但是后面去修改key的值,没有加上过期时间,此时key的过期时间会被擦除。
2)淘汰策略使用了定时删除
缓存中的key到了时间时,如何被删除。redis同时使用惰性删除和定时删除。
惰性删除
expireIfNeeded
当读取一个key时,判断是否过期,过期则删除。
优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
缺点:冷数据无法及时删除容易造成内存泄漏。
如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放。从而造成内存泄漏。
定时删除
activeExpireCycle
每隔一段时间,扫描缓存expires过期字典中一定数量的key,删除里面过期的key。
修改配置文件redis.conf 的 hz 选项来调整这个次数,默认100ms运行一次,1秒10次。
Redis每秒10次做的事情:
- 测试随机的20个keys进行相关过期检测。
- 删除所有已经过期的keys。
- 如果有多于25%的keys过期,重复步奏1.
优点:定时扫描的时间间隔与每次扫描的限定耗时,可以再不同情况下使CPU与内存资源达到最优的平衡效果。
缺点:难以确定删除操作执行的时长和频率。
安装配置
安装
下载安装包
下载redis安装包,放在 root ⽬录下 。
wget http://download.redis.io/releases/redis-4.0.1.tar.gz
解压安装包
1、在 /usr/local/ 下创建 redis ⽂件夹并进⼊
2、将 Redis 安装包解压到 /usr/local/redis 中即可
解压完之后, /usr/local/redis ⽬录中会出现⼀个 redis-5.0.8 的⽬录
编译并安装
cd redis-5.0.8/
make && make install
将 REDIS 安装为系统服务并后台启动
cd utils/
./install_server.sh
查看REDIS服务启动情况
systemctl status redis_6379.service
启动REDIS客户端
redis-cli
设置允许远程连接
安装报错及解决方案:
make[1]: Entering directory `/home/liuchaofan/redis-3.0.7/src'
CC adlist.o
/bin/sh: cc: command not found
make[1]: *** [adlist.o] Error 127
make[1]: Leaving directory `/home/liuchaofan/redis-3.0.7/src'
make: *** [all] Error 2
解决方案:
yum install gcc
或者
yum -y install gcc gcc-c++ libstdc++-devel
make[1]: Entering directory `/home/liuchaofan/redis-3.0.7/src'
CC adlist.o
adlist.c:1: error: CPU you selected does not support x86-64 instruction set
make[1]: *** [adlist.o] Error 1
make[1]: Leaving directory `/home/liuchaofan/redis-3.0.7/src'
make: *** [all] Error 2
解决方案:
make CFLAGS="-march=x86-64"
配置
Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf