缓 存

缓存

为了解決不同介质的响应速度和成本差异的问题。不同介质包括磁盘、内存、网络等。缓存的使用可以提高系统的读取速度和性能。

数据存储介质寻址I/O buffer带宽持久性
磁盘msG/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
-k1=keep alive 0=reconnect1
-rSET/GET/INCR 使用随机 key, SADD 使用随机值
-P通过管道传输 请求1
-q强制退出 redis。仅显示 query/sec 值
–csv以 CSV 格式输出
-l生成循环,永久执行测试
-t仅运行以逗号分隔的测试命令列表。
-IIdle 模式。仅打开 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。
LRULFU
Least Recently UsedLeast 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. 耗时:更新缓存>写数据库+删缓存。

绝大多数情况下,写数据请求比读数据情况耗时更长。系统同时满足上述两个条件的概率非常小。

问题:

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(成员总的体积过大)。

影响:緩存性能下降。

  1. 单个请求占用10和带宽影响缓存中间件整体吞吐量
  2. 内存使用不均匀
  3. 网络拥塞

方案:

  1. 可以将大key拆分成多个小key进行存储
  2. 数据压缩
  3. 采用多级缓存,在应用内加本地缓存。
热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次做的事情:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于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

多级缓存

JetCache

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值