缓存方案对比
Ehchache
优点:
- 基于java开发
- 基于jvm缓存
- 简单、轻巧、方便
缺点:
- 不支持集群
- 不支持分布式(适用于单体应用)
Memcache
优点:
- 简单的key-value存储(只能存储String类型数据)
- 内存使用率较高
- 多核处理,多线程
缺点:
- 无法容灾
- 无法持久化
Redis
优点:
- 丰富的数据结构
- 可以持久化
- 支持主从同步、故障转移
- 内存数据库
缺点:
- 单线程(数据量过大后效率低)
- 单核
Redis安装
http://redisdoc.com/ redis命令百科地址
-
下载安装包:https://redis.io/download/
-
安装依赖:yum install gcc-c++
-
编译:make
-
安装:make install
-
修改启动脚本:
cp ./utils/redis_init_script /etc/init.d/
..... #开机自启动命令 #chkconfig: 22345 10 90 #description: Start and Stop redis REDISPORT=6379 EXEC=/usr/local/bin/redis-server CLIEXEC=/usr/local/bin/redis-cli PIDFILE=/var/run/redis_${REDISPORT}.pid #配置文件地址: CONF="/usr/local/redis/redis.conf" ... stop) if [ ! -f $PIDFILE ] then echo "$PIDFILE does not exist, process is not running" else PID=$(cat $PIDFILE) echo "Stopping ..." #如果设置了密码需要在这里添加密码,才能使用脚本进行停止 $CLIEXEC -a "lhy" -p $REDISPORT shutdown while [ -x /proc/${PID} ] do echo "Waiting for Redis to shutdown ..." sleep 1 done echo "Redis stopped" fi ;; *) echo "Please use start or stop as first argument"
-
修改配置文件:
mkdir /usr/local/redis -p cp redis.conf /usr/local/redis/
#redis 后台运行 daemonize yes #工作目录 dir /usr/local/redis/db #绑定启动ip,允许外部机器链接 bind 0.0.0.0 #链接密码 requirepass lhy #启动端口 port 6379 #默认有16个数据库。默认使用的是第0个 databases 16
-
通过启动脚本启动redis:
./redis_init_script start
-
加入开机自动启动:
chkconfig redis_init_script on
客户端工具命令
- 链接客户端:redis-cli
- 密码认证:AUTH lhy
- 查看远程服务器上redis是否存活:
redis-cli -a password ping
,响应 PONG 为存货状态 - 关闭redis:redis-cli -a password shutdown
- 通过脚本停止:./redis_init_script stop
- SELECT index 切换数据库
- FLUSHDB 清空当前数据库所有数据
- FLUSHALL 清空所有数据库的数据
- TYPE key 查询key的数据类型
数据类型
5大数据类型:String,hash,list,set,zset
-
String
- set key value 添加数据
- get key 获取key的value
- del key 删除key
- setnx key value 如果key不存在才会设置值
- ttl key 查询key的过期时间。值如果为 -1 永不过期
- EXPIRE key time 给key设置过期时间
- APPEND key value 给key的值尾部拼接value
- incr key 值递增1
- decr key 值递减1
- INCRBY key value 值累加value
- DECRBY key value 值累减value
- GETRANGE key start end 截取值然后返回
- SETRANGE key offset value 替换值,将offset处的字符替换成value
- mset key value [key value …] 同时设置多个键值对
- mget key [key …] 同时获取多个key的值
-
hash (类似map,或者类似java中的一个对象)
- hset key field value 设置一个hash,名为key,属性为field,值为value例如: hset user name luohengyi age 10
- hget key field 获取key的field 属性的值
- HMGET key field [field …] 获取hash的多个属性值
- HGETALL key 获取hash的所有属性
- HKEYS key 获取某个hash所有的属性名称
- HVALS key 获取某个hash所有的属性值
- HEXISTS key field 判断hash是否拥有某个属性
- HDEL key field 删除
- HDEL key field [field …] 删除hash的属性
-
list
- LPUSH key value [value …] 从左侧放入元素
- RPUSH key value [value …] 从右侧放入元素
- LPOP key 从左侧拿出一个元素
- RPOP key 从右侧拿出一个元素
- LLEN key 获取list的长度
- LINDEX key index 查询list的某个下标的元素
- lset key index value 替换list某个下表的值
- LINSERT key BEFORE|AFTER pivot value 在某个值的前方/后方插入一个值
- LREM key count value 如果有相同值,删除一个的方式,count删除第几个,value要删除的值
- LTRIM key start stop 保留截取的元素
-
set 无需不重复数组
- SADD key member [member …] 添加元素
- SMEMBERS key 查询集合中的所有元素
- SISMEMBER key member 查询元素是否存在于集合
- SREM key member 删除某个元素
- SPOP key 从尾部拿出一个元素
- SRANDMEMBER key [count] 随机查询出count个元素
- SMOVE source destination member 移动元素,将source 中的member 移动到destination 中
- SDIFF key [key …] 获取多个集合的差集(以第一个为准)
- SINTER key [key …] 获取多个集合的交集
- SUNION key [key …] 获取多个集合合并后的数据
-
zset 有序不重复
- ZADD key score member [score member …] 添加元素,一个元素包含一个分数score 和一个数据member
- ZRANGE key 0 -1 查询集合内所有元素
- ZRANGE key0 -1 withscores查询集合内所有元素(包含分数)
- ZRANK key member 获取元素下标
- ZSCORE key member 获取元素分数
- ZCARD key 统计集合总数
- ZCOUNT key min max 统计分数在区间内的数量
- ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 获取分数区间内的元素,LIMIT 进行分页
- ZREM key member 删除元素
线程模型
IO多路复用:IO多路复用
其实就是我们说的select
,poll
,epoll
,它的基本原理就是select
,poll
,这个function会不断的轮询所负责的所有socket
,当某个socket
有数据到达了,就通知用户进程,epoll是基于事件驱动得不需要轮询。select和poll会返回所有得socket(只不过操作系统会将准备就绪的socket做上标识,用户层将不会再有无意义的系统调用开销。),只有epoll(基于事件驱动)会返回发生了事件得链接。
select方法调用模型:
epoll方法调用模型:
redis线程模型:
SpringBoot整合
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
数据源配置
spring:
redis:
database: 1
host: 172.16.100.131
port: 6379
password: 123123
使用方式
@Autowired
// RedisTemplate 会出现乱码 使用 StringRedisTemplate
private RedisTemplate redisTemplate;
@GetMapping("/set")
public Object setObject(
String key,
String value
) {
// string 类型的 操作使用 opsForValue,其他数据类型分别对应
// opsForHash 等
redisTemplate.opsForValue().set(key, value);
return null;
}
发布和订阅
- SUBSCRIBE channel 订阅,channel 为订阅得平道
- PUBLISH channel message 向某个频道发送消息
持久化机制
RDB
适合大量数据的恢复,但是数据的完整性和一致性可能会不足。
时间间隔执行数据集的时间点快照。配置文件中dir /usr/local/redis/working
为备份文件地址
-
优势
备份的临时文件可以以时间节点的方式存储,可以恢复不同时间点的数据。
RDB非常适合灾难恢复,它是一个可以传输到远端数据中心或Amazon S3(可能是加密的)的压缩文件。
备份使用子进程来工作的不影响工作性能。
RDB允许在大数据集的情况下更快地重启。
子进程备份的时候,主进程不会有任何io操作(不会有写入修改或删除),保证备份数据的的完整性。
-
缺点
你通常会每5分钟或更长的时间创建一个RDB快照,如果redis非正常关闭最后一次的快照将无法形成,这回导致一部分数据丢失。
数据大时,对cpu损耗较大。
-
配置:
# 这个配置可以天根据自己的需求新增修改 # 发生至少1次更改后会在 3600秒(1个小时)后快照一次 save 3600 1 # 发生至少100次更改后会在 300秒(5分钟)后快照一次 save 300 100 # 发生至少10000次更改后会在 60秒(1分钟)后快照一次 save 60 10000 #备份文件地址 dir /usr/local/redis/working #备份文件名称 dbfilename dump.rdb #默认值为yes。当启用了RDB且最后一次后台保存数据失败,redis会停止接收set的命令。重启后可以再次接收set命令 stop-writes-on-bgsave-error yes #默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用 #LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的#快照会比较大。 rdbcompression yes #默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增 #加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能 rdbchecksum yes
AOF
记录服务器接收到的每个写操作,这些操作将在服务器启动时再次播放,重建原始数据集。命令记录使用与Redis协议本身相同的格式,以一种仅附加的方式。当日志变得太大时,Redis能够在后台重写日志。
-
优点
-
如果不小心删除了所有数据,可以将日志中的这行删除日志删除然后重启服务器,以恢复数据
-
日志发生意外:存储空间不足时,记录失败,也可以使用工具修复
-
日志记录共有2个方式,没次写操作都记录日志,间隔1秒编写日志
-
日志文件过大时会进行重写
重写aof文件并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件,这一点和快照有点相似
-
-
缺点
- 日志文件相对较大
- 如果日志模式为没次写操作都记录一次那么io开销会很大,如果日志记录模式为每秒一次那么性能不会有太大的影响
-
配置
#开启 AOF 模式,默认关闭 appendonly yes #日志文件名称 appendfilename "appendonly.aof" #模式 appendfsync everysec # always 每次写操作都去记录日志,影响效率但是很安全 # everysec 每秒写一次日志 # no 关闭 # -----------# # no-appendfsync-on-rewrite no #当发生日志重写时,刚好发生日志写入操作会引发io冲突造成io阻塞 ,使用no-appendfsync-on-#rewrite解决: #no 最安全的方式,不会丢失数据,但是要忍受阻塞的问题。写入日志和重写发生io冲突从而影响效率 #yes 则在执行重写时不写入日志但是如果发生意外可能导致日志丢失 # 当前的aof文件与上次的文件比较相差多少100(1倍)并且当前文件大于65mb(kb,mb,gb)时触发重写 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 65mb
主重架构
主从原理,slave 端启动后发送ping包到master端,master从内存中 复制出一个rdb文件返回,slave下载到磁盘中再读取到自己的内存中,在master中随后的写操作命令都会发送到slave端,以保证数据一致,在数据同步时不会影响master写和slave的读操作,他们会使用老的数据提供服务。
一般使用1主2从,使用过多的slave从库会导致,内网带宽的过多使用。压力过大时可以使用树状的结构分散复制的压力
使用redis命令:info replication 查看主从信息
# Replication
role:slave
master_host:192.168.2.118
master_port:6379
#同步状态
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:291544
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:d3d36a8a51e7c51661ffbe1976460e1b94cf54d9
master_replid2:db85919371e2ac47fa63d61cb2d0a9d755782bfe
master_repl_offset:291544
second_repl_offset:1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:291544
从节点配置:配置完了最好删除当前节点的所有数据以及备份文件
#主几点ip
replicaof 192.168.2.119 6379
#主节点密码
masterauth lhy
#从节点只读
replica-read-only yes
重启后查询主从状态:
从节点可以查看主节点信息
info replication
# Replication
role:slave
master_host:192.168.2.119
master_port:6379
master_link_status:up
主节点可以查看从节点信息
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.2.118,port=6379,state=online,offset=378,lag=0
slave1:ip=192.168.2.120,port=6379,state=online,offset=378,lag=0
master_replid:bf5b7119150bb2a4635d50e3947d4a8afa635ecd
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:378
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:378
Redis复制原理
- 基于磁盘化的复制,master的数据通过rdb的方式导出到磁盘。slave请求master节点获取到rdb文件再导入到自己的内存中。
-
无磁盘化复制,slave直接通过socket链接到master进行数据传输(master创建一个新的进程,该进程直接将rdb复制文件直接写入socket流中,从而避免磁盘的写入)。避免数据较大时,由于服务器是机器磁盘文件读写慢的问题,处于实验阶段不建议生产上使用。使用配置开启:
repl-diskless-sync yes
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVqGSN7u-1675923490158)(…/…/image\image-20230206134728317.png)]
缓存过期机制
-
主动,定期删除
默认每秒10次,随机抽查key是否过期
-
被动,惰性删除
当客户端请求一个key的时候,那么redis会检查这个key是否过期,如果过期了,则删除,然后返回一个nil。这种策略对cpu比较友好,不会有太多的损耗,但是内存占用会比较高。
内存淘汰机制
设置redis最大可使用的内存大小,如果不设置那么redis会一直向os一直申请内存,直到内存使用完。如果内存使用完了,redis提供了一系列的处理方式默认 noeviction ,新的缓存设置不了直接返回错误
#设置redis最大可使用的内存大小。
maxmemory <bytes>
maxmemory-policy noeviction
内存淘汰机制
- noeviction:旧缓存永不过期,新缓存设置不了,返回错误
- allkeys-lru:清除最少用的旧缓存,然后保存新的缓存(推荐使用)
- allkeys-random:在所有的缓存中随机删除(不推荐)
- volatile-lru:在那些设置了expire过期时间的缓存中,清除最少用的旧缓存,然后保存新的缓存
- volatile-random:在那些设置了expire过期时间的缓存中,随机删除缓存
- volatile-ttl:在那些设置了expire过期时间的缓存中,删除即将过期的
哨兵机制
将redis集群监控起来,当master挂掉以后,从slave节点中选举一个master出来从而实现redis的高可用。,为了防止哨兵的错误,将哨兵部署为集群。核心配置文件。sentinel.conf.
配置
#绑定ip,指定这个ip才能访问
# bind 127.0.0.1 192.168.1.1
#接触访问控制
protected-mode no
#访问端口
port 26379
#后台启动
daemonize yes
#日志文件地址
logfile /usr/local/redis/redis-sentinel.log
#工作空间
dir /usr/local/redis/sentinel
#核心配置,master的ip以及端口
#sentinel monitor <master-group-name> <ip> <port> <quorum>
#配置监听的master的ip端口并取别名(有效的字符集是A-z 0-9和三个字符.-_)
#配置指示 Sentinel 去监视一个名为 mymaster 的主服务器, 这个主服务器的 IP 地址为 127.0.0.1 , 端口号为 6379 , 而将这个主#服务器判断为失效至少需要 <quorum>2 个 Sentinel 同意
sentinel monitor mymaster 127.0.0.1 6379 2
# 配置redis密码,sentinel auth-pass <master-name> <password>,主节点密码和slaver密码应该一致
sentinel auth-pass lhy-master lhy
#当节点失效多少毫秒 sentinel 判定该节点失效
#sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds lhy-maste 10000
#这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时
#间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave #处于不能处理命令请求的状态。
sentinel parallel-syncs lhy-maste 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 lhy-master 180000
配置范例
port 26379
pidfile "/usr/local/redis/sentinel/redis-sentinel.pid"
dir "/usr/local/redis/sentinel"
daemonize yes
protected-mode no
logfile "/usr/local/redis/sentinel/redis-sentinel.log"
# 配置哨兵
sentinel monitor mymaster 127.0.0.1 6379 2
# 密码
sentinel auth-pass mymaster password
# master被sentinel认定为失效的间隔时间
sentinel down-after-milliseconds mymaster 30000
# 剩余的slaves重新和新的master做同步的并行个数
sentinel parallel-syncs mymaster 1
# 主备切换的超时时间,哨兵要去做故障转移,这个时候哨兵也是一个进程,如果他没有去执行,超过这个时间后,会由其他的哨兵来处理
sentinel failover-timeout mymaster 180000
**启动哨兵命令:**redis-sentinel ./sentinel.conf
启动后在日志中可观察到其他哨兵节点的加入:
59071:X 08 Feb 2023 00:18:57.657 * +sentinel sentinel d1afbcd1418d46fc54b69176fdf3836d563f1d8c 192.168.2.118 26379 @ mymaster 192.168.2.119 6379
59071:X 08 Feb 2023 00:20:28.005 * +sentinel sentinel 372dbcb3b3d32a3c67e0cf53f4ad7394b41718b5 192.168.2.120 26379 @ mymaster 192.168.2.119 6379
哨兵节点管理
使用命令: redis-cli -p 26379 链接到哨兵
查看redis节点信息
-
查看imooc-master下的master节点信息
sentinel master imooc-master
-
查看imooc-master下的slaves节点信息
sentinel slaves imooc-master
-
查看imooc-master下的哨兵节点信息
sentinel sentinels imooc-master
查看哨兵信息
- sentinel sentinels imooc-master
master恢复后数据不同步问题
-
由于该节点开始是master节点不需要同步数据,挂掉后重启后会变为slave节点,此时需要配置
masterauth
配置访问主节点的密码#主节点密码 masterauth lhy
一般master数据无法同步给slave的方案检查为如下:
- 网络通信问题,要保证互相ping通,内网互通。
- 关闭防火墙,对应的端口开放(虚拟机中建议永久关闭防火墙,云服务器的话需要保证内网互通)。
- 统一所有的密码,通过逐台检查机器以防某个节点被遗漏。
spring集成哨兵
配置哨兵信息后,redis的master信息会通过哨兵去获取
spring:
redis:
database: 1
password: imooc
sentinel:
master: imooc-master
nodes: 192.168.1.191:26379,192.168.1.192:26379,192.168.1.193:26379