Redis版本:redis-5.0.5.tar.gz
Linux发行版:CentOS-7-x86_64-DVD-1804.iso
Linux基础操作:https://blog.csdn.net/u011424614/article/details/94555916
“ #-- ” 表示注释
“ # ” 表示终端命令
“ > " 表示 redis-cli 命令
Redis 是一个key/value的内存缓存数据库
结构:
# cd /home/user1/下载
# cp redis-5.0.5.tar.gz /opt/redis/
# cd /opt/redis/
# tar -xvzf cp redis-5.0.5.tar.gz
# yum install gcc
# cd redis-5.0.5
#-- 第一种安装方式
# make & make install
# cd src
#-- 第二种安装方式
# make MALLOC=libc
# make test
# cd src && make install
#-- 启动服务端和客户端
# ./redis-server
# ./redis-cli
#-- 需要配置文件可以拷贝 redis根目录下的 redis.conf 文件到 src
下载:https://github.com/MicrosoftArchive/redis/releases
将 Redis 添加到服务的命令:redis-server --server-install redis.windows.conf
–loglevel verbose
Redis命令:http://redisdoc.com/index.html
#-- 查看redis实例的有哪些key(不建议生产系统使用,会阻塞redis实例)
> keys *
#-- 查看 key 对应 value 的数据类型
> type [key]
#-- 查询 key 是否存在
> exists [key]
# -- 删除 key
> del [key]
应用场景:session统一存储、ip限制(incr 原子递增)
结构:key / value
可存储类型:字符串、整数、浮点
Redis 内部数据结构:
src/sds.c(C语言)
int / SDS (simple dynamic string,可修改的字符串,采用预分配冗余空间的方式来减少内存的频繁分配),整数使用int存储,字符串和浮点使用sds存储
符合二进制安全和任意长度(根据数据长度自动选择数据格式, sdshdr8的长度是 28-1,sdshdr16的长度是216-1,一共5种类型)(sdshdr8:len、alloc、flag、buf[]),字符串结尾 ’ \0 ’
sdsnewlen 方法,根据数据长度,判断出结构类型
#-- 添加值
> set [key] [value]
#-- 获取值
> get [key]
#-- 左添加
> lpush mylist
#-- 右添加
> rpush mylist
#-- 左获取数据,并删除
> lpop mylist
#-- 右获取数据,并删除
> rpop mylist
#-- 从左边开始,查询范围内的值
> lrange mylist 0 -1
#-- 同时添加多个数据
> lpush 1 2 3 4
#-- 添加数据
> hset userinfo name=zcheng
> hset userinfo age=18
#-- 获取key中的数据
> hget userinfo name
#-- 批量添加数据
> hmset [key] [field] [value] [field] [value]
#-- 获取key中的所有数据
> hgetall userinfo
#-- 查询是否存在字段
> hexists userinfo birthday
#-- 添加数据
> sadd website "csdn.com" "baidu.com"
#-- 获取数据
> smembers website
> deff [key]
#-- 设置值,搜索引擎的分数
> zadd page_rank 9 google.com 8 baidu.com 7 bing.com
#-- 获取值,google.com 的分数
> zscore page_rank google.com
#-- 获取范围内的数据
> zrange page_rank 0 3
> zrange page_rank 0 3 withscores
#-- 排序,上面是升序,下面是降序
> zrevrange page_rank 0 3 withscores
#-- 设置超时时间
> expire [key] [seconds]
> setex(string key, int seconds, string value)
#-- 查询 key 的过期时间变化(-2 表示过期, -1 未设置过期时间)
> ttl [key]
#-- 取消过期时间设置
> persist [key]
原理:
消极方法(passive way):当应用访问 key 时,发现 key 是过期的,这时才删除 key
积极方法(active way):周期性的检测设置可过期时间的 key,检测到 key 过期时,就会删除一部分,这里只删除一部分是因为,检测的 key 是随机选择的,并不会检测全部
1.随机检测 20 个带有 timeout 标识的 key;2.如果超过 25% 的key,被删除,则重复执行整个流程
How Redis expires keys
Redis keys are expired in two ways: a passive way, and an active way.
A key is passively expired simply when some client tries to access it, and the key is found to be timed out.
Of course this is not enough as there are expired keys that will never be accessed again. These keys should be expired anyway, so periodically Redis tests a few keys at random among keys with an expire set. All the keys that are already expired are deleted from the keyspace.
Specifically this is what Redis does 10 times per second:
- Test 20 random keys from the set of keys with an associated expire.
- Delete all the keys found expired.
- If more than 25% of keys were expired, start again from step 1.
#-- 通过频道发布消息
> publish channel.hello world
#-- 订阅频道获取消息
> subscribe channel.hello
把 redis 当成 NoSQL 来使用
当 redis 在分布式下大规模应用时,防止由于缓存失效,大量的请求直接打到数据库上,导致数据库无法承受(雪崩效应)
RDB
快照:当符合条件的时候,fork子进程,把数据备份到临时文件,持久化完成后,会生成一个快照文件 dump.rdb;如果以及存在快照文件,会对快照文件进行覆盖
由于是fork子进程进行操作,所以对主进程的数据操作不会产生影响
四种条件:
1.配置规则
#-- save [seconds] [changes] 在900秒内,如果有1个更改,就会触发一次快照
#-- 这里的三个快照是 或 的关系,只有满足配置就会触发
save 900 1
save 300 10
save 60 10000
2.主动执行 save 命令 (会阻塞所有客户端请求) 或者 bgsave 命令 (异步执行,不会阻塞客户端请求)
> save
> bgsave
3.flushall (清空所有内存数据,如果配置规则存在,则执行一次快照操作)
4.执行复制操作 (主从数据的复制,完成数据库同步)
缺点:在触发下一次快照期间产生了数据,如果这时 redis 服务停止运行,会导致这期间的数据丢失,而且,fork 子进程会消耗服务器性能
AOF
日志:实时的将 redis 的变更数据操作进行备份
启动 AOF 的方式:redis.conf 搜索 /append
#-- 修改 appendonly no
appendonly yes
停止 redis 服务端,重新启动
# ./redis-server redis.conf
AOF 启动后,默认会从 AOF 恢复数据和持久化数据;可以通过 vim 指令查看文件内容
# vim appendonly.aof
问题:如果操作过多的话,会导致AOF文件越来越大
解决方法:redis.conf 搜索 /auto
#-- 当前AOF文件超过上一次AOF文件的百分之几时,进行重写
auto-aof-rewrite-percentage 100
#-- 如果小于设置值时,没有必要重写
auto-aof-rewrite-min-size 64mb
在重写时,会fork子进程进行重新,重写时不会对原来的AOF文件进行重写,而是对内存的数据进行重写,如果在重写期间,有新的指令,redis 会把这些指令先加到重写缓存中,内存数据重写完毕后,将重写缓存中的指令追加到重写文件中,既生成新的AOF文件
redis.conf 搜索 /everysec
#-- 操作指令什么时候同步到磁盘文件里面
#--(由于操作系统有自己的缓存机制,操作指令是不会直接添加到磁盘文件中,而是保存到硬盘缓存)
#-- 默认每秒同步一次(到磁盘),always表示每次操作都进行同步,no表示不主动同步
appendfsync everysec
缺点:原来只要把数据写入内存就可以,现在还有把变更操作保存到AOF文件,会有性能损耗
两种方式选择:
两种方式都配置,会同时生效(同时兼并两种方式的优点)
AOF可以保障数据的安全性(记录每一次变更操作)
RDB的数据恢复速度会更快
文件损坏恢复:
redis-check-aof、redis-check-rdb
#-- 内存回收策略,noeviction表示当达到最大内存,在申请内存时会报错;redis.conf中配置maxmemory
#-- allkeys-lru 表示从内存库中挑选最少使用的key进行回收
#-- allkeys-random 表示对所有的key进行随机回收
#-- volatile-random 表示从设置了过期时间的key集里面进行随机回收
#-- volatile-lru 表示从设置了过期时间的key集里面,选择最少使用的key进行回收
#-- volatile-ttl 表示挑选即将过期的key进行回收
# maxmemory-policy noeviction
redis 的瓶颈不在CPU的多核心资源,主要是内存和网络
同步和异步指用户线程和内核的交互方式,阻塞和非阻塞指用户线程调用内核进行IO操作时产生
OSI(Open System Interconnection)七层协议中,传输层以上(不包括传输层)为用户空间,传输层以下(包括传输层)为内核空间
异步阻塞IO(IO多路复用):用户线程请求内核数据时,如果内核空间没有准备好数据,用户线程会启动一个监听,当内核数据准备好了后,通知用户线程的监听(多路:同一个通道多次使用,不用担心阻塞)
问题:单线程redis为什么不能保证原子性?多个客户端同时读取一个数据进行修改时,redis对执行顺序是没有约束的,最终的修改结果是无法预判的
1.redis 使用了多路复用机制(事件机制)
2.redis 中大部分是内存操作
3.单线程避免了线程竞争和上下文切换的开销(缺点:无法利用好CPU资源)(每个机器是可以部署多个redis的,毕竟每个redis只用了一个核心处理任务)
-- 调用 redis 指令, 添加 string 类型值
redis.call('set','name','zcheng')
-- 获取返回值
local val = redis.call('get','name')
> eval "return redis.call('get','name')" 0
#-- 设置动态参数
> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 id 123456
return redis.call('get','name')
运行脚本:./redis-cli --eval test.lua
限制某个 IP 的访问次数
-- 定义 key 变量,'..'表示拼接,KEYS[1]表示第一个KEYS参数
local key = "ratelinit:"..KEYS[1]
-- 限制次数
local limit = tonumber(ARGV[1])
-- 超时时间,秒
local expireTime = ARGV[2]
-- 获取key的执行次数,如果key不存在,会自动创建,并返回1
local times = redis.call('incr',key)
-- 如果第一次进来,需要对key设置过期时间
if times == 1 then
redis.call('expire',key,expireTime)
end
-- 如果访问次数超过限制次数,则返回0
if times > limit then
return 0
end
-- 正常访问
return 1
运行脚本(10秒内10次):./redis-cli --eval ratelimit.lua 192.168.11.53 , 10 10
(开头的要求1)问题:由于 lua 脚本中可能存在大量的指令和逻辑,当在客户端执行lua脚本时,会将lua脚本通过网络传输到服务端,这个网络传输是非常消耗网络资源的,那么该如何解决?
答案:对lua脚本设置摘要(sha-1算法)
#-- 生成 lua 脚本的摘要,lua 脚本会保存到 redis 的脚本缓存中
> script load "return redis.call('get','name')"
#-- 通过摘要,调用lua脚本,没有参数 0
> evalsha "52da8c7de39385e305fb1af2a8ffd21534af996f" 0
#-- 死循环的 lua 脚本(其它客户端执行redis指令时,会提示正在执行脚本,也就是阻塞了)
> eval "while true do end" 0
#-- 终止脚本执行
> script kill
-- 修改值
redis.call('set','name','zchengl')
while true do end
#-- 终止 redis 服务,lua脚本是执行失败的
> shutdown nosave
#-- 需要重新启动 redis 服务
具体操作
# replicaof
replicaof 192.168.31.52 6379
# bind 127.0.0.1 #(允许其它机器访问)注释掉不修改内容也可以
bind 0.0.0.0
# protected-mode yes #(关闭保护模式)
protected-mode no
# firewall-cmd --zone=public --list-ports
# firewall-cmd --zone=public --add-port=6379/tcp --permanent
# firewall-cmd --reload
(marster 操作)
# ./redis-cli
> info replication
------------------------ 输出信息
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.31.23,port=6379,state=online,offset=938,lag=0
slave1:ip=192.168.31.64,port=6379,state=online,offset=938,lag=0
master_replid:10f86211304c21ec4862687c30d5b000074e9231
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:938
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:938
(slave 操作)
# ./redis-cli
> info replication
------------------------ 输出信息
# Replication
role:slave
master_host:192.168.31.52
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:1106
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:10f86211304c21ec4862687c30d5b000074e9231
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1106
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1106
(error) READONLY You can't write against a read only replica.
原理
1.全量复制:
启动一个 slave 节点后,slave 节点会发送同步命令(SYNC) 到 master 节点,master 节点执行 bgsave 命令生成 rdb 快照,master 节点将 rdb 快照文件返回给 slave 节点,最后 slave 节点加载快照;至于执行 bgsave 指令之后的数据,master 会先添加到缓存,然后,把数据发送给 slave 节点,类似一个增量复制
如果配置了主从复制,就算禁用了 rdb,也会生成 rdb 快照进行数据同步
slave 节点开启监听
> replconf listening-port 6379
> sync
> set test 123456
SYNC with master, discarding 213 bytes of bulk transfer…
SYNC done. Logging commands from master.
“set”,“test”,“123456”
“PING”
* 问题:master 同步完多少个slave节点后,才会对外提供服务?
* 答案:master 的 redis.conf 配置 `min-slave-to-write 3` 表示最少3个slave节点连接到master节点,master节点才能进行写操作,这时master才会对外服务,`min-slave-max-lag 10` 表示允许slave最长的丢失连接时间,这里如果10秒内master没有接送都salve的心跳包反馈,就会认为slave断开连接
* 2.增量复制
* 问题:当网路延迟,导致数据不一致时,怎么同步数据?
* 答案:增量复制
* 增量复制需要保证,当前同步的位置是上一次同步中断的位置
```terminal
> info replication
#--以下内容记录同步的位置(增量复制出现在redis 2.8以后)
#--(master和slave都会保存,如果slave断开连接后,重新连接,会从offset位置开始同步)
master_repl_offset:1106
repl_backlog_active:1
3.无磁盘复制
#-- no表示不生成磁盘快照,直接数据传输(无磁盘复制出现在redis 2.8以后)
repl-diskless-sync no
选择:(前期)全量复制和(后期)增量复制是同时使用的,而无磁盘复制是可选的
通常中间件集群的高可用:1.内部leader选举,2.依托外部的机制,而redis是第二种,既哨兵机制
作用:1.监控master和slave的运行情况,2.当master出现故障时,从集群的slave中选举一个新的master
为了保证哨兵的高可用,哨兵支持集群,哨兵之间相互监控和感知
具体操作
#-- 端口
port 26379
#-- 监控master节点,IP:对应master的IP,端口默认,
#-- 2表示当master出现故障时,多少个sentinel达成一致,才会认为master是故障的
# sentinel monitor mymaster 127.0.0.1 6379 2
sentinel monitor mymaster 192.168.31.52 6379 1
#-- 设置 master 停止运行后多久触发
# sentinel down-after-milliseconds (可能需要注意顺序)
sentinel down-after-milliseconds mymaster 5000
#-- 在设置的时间内,master没有复活,触发failover机制,重新选举master
# sentinel failover-timeout
sentinel failover-timeout mymaster 15000
./redis-sentinel ./sentinel.conf
#-- sentinel主观认为master停止运行
# +sdown master mymaster 192.168.31.52 6379
#-- sentinel客观认为master停止运行(真正认为停止运行)
# +odown master mymaster 192.168.31.52 6379 #quorum 1/1
# +new-epoch 3
# +try-failover master mymaster 192.168.31.52 6379
# +vote-for-leader a1c63e0f95bbf58660980ff3028ded2b83efbf74 3
# +elected-leader master mymaster 192.168.31.52 6379
# +failover-state-select-slave master mymaster 192.168.31.52 6379
# +selected-slave slave 192.168.31.64:6379 192.168.31.64 6379 @ mymaster 192.168.31.52 6379
* +failover-state-send-slaveof-noone slave 192.168.31.64:6379 192.168.31.64 6379 @ mymaster 192.168.31.52 6379
* +failover-state-wait-promotion slave 192.168.31.64:6379 192.168.31.64 6379 @ mymaster 192.168.31.52 6379
# +promoted-slave slave 192.168.31.64:6379 192.168.31.64 6379 @ mymaster 192.168.31.52 6379
# +failover-state-reconf-slaves master mymaster 192.168.31.52 6379
* +slave-reconf-sent slave 192.168.31.23:6379 192.168.31.23 6379 @ mymaster 192.168.31.52 6379
* +slave-reconf-inprog slave 192.168.31.23:6379 192.168.31.23 6379 @ mymaster 192.168.31.52 6379
* +slave-reconf-done slave 192.168.31.23:6379 192.168.31.23 6379 @ mymaster 192.168.31.52 6379
# +failover-end master mymaster 192.168.31.52 6379
# +switch-master mymaster 192.168.31.52 6379 192.168.31.64 6379
* +slave slave 192.168.31.23:6379 192.168.31.23 6379 @ mymaster 192.168.31.64 6379
* +slave slave 192.168.31.52:6379 192.168.31.52 6379 @ mymaster 192.168.31.64 6379
# +sdown slave 192.168.31.52:6379 192.168.31.52 6379 @ mymaster 192.168.31.64 6379
# Next failover delay: I will not start a failover before Sun Jul 21 15:06:58 2019
* +convert-to-slave slave 192.168.31.52:6379 192.168.31.52 6379 @ mymaster 192.168.31.23 6379
[外链图片转存失败(img-TJUe3R1B-1567219901238)(.\assets\img002.jpg)]
最小规模分片集群:6台机器,3个master和3个slave,一个master对应一个slave
最最最小分片集群:3台机器,3个master(没有了热备,降低了集群的可用性)
gossip 协议的无中心化节点的集群
redis 分片集群通信组件:redis cluster bus
虚拟槽的概念:0 ~ 16383(slot)
set test 123456
,这是会使用 CRC16(key)%16383
计算出一个值,表示路由到对应虚拟槽区间的机器上,保证数据可以合理的保存到多个节点上,例如:计算值=1000,表示数据路由到虚拟器区间为0~5000的机器上问题:做了redis-cluster集群后,如果想把相同业务的数据路由到同一台机器上,需要怎么做?
答案:HashTag,例如:user:{user123}:id,user:{user123}:name,user:{user123}:age,以上数据中,redis会把 {} 内的内容认为是一个HashTag,当执行 CRC16算法时,参数为 HashTag 的内容,从而使得计算值相同,也就是会路由到同一台机器上
问题:假设:name数据保存在一号机器上,如果这时发送get请求到三号机器,redis会怎么处理?
答案:三号机器会返回 MOVED 信息(MOVED <1号机器的IP>:<1号机器的redis端口>),然后在根据MOVED信息进行重定向到1号机器获取数据
问题:3号机器怎么知道数据在1号机器?
答案:查看上面机器结构图可以知道,集群之间是相互通信的
问题:怎么做分片迁移?
答案:分片迁移可能是增加了机器,这时需要解决虚拟槽的分配和数据的迁移
虚拟槽的分配
数据的迁移
企业级分片集群选择:condis (优点:多CPU的支持,动态扩容,分片路由) / twemproxy (优点:成熟的解决方案,缺点:无法动态扩容) / redis-cluster
集群配置(3主6从)
安装目录:/opt/redis-cluster/redis-5.0.5
# mkdir cluster
# cd cluster
# mkdir 7000 7001 7002 7003 7004 7005 7006 7007 7008
# cp redis.conf cluster/7000/redis-7000.conf
# vim redis-7000.conf
#-- 注释bind 或改为 0.0.0.0
# bind 127.0.0.1
#-- 取消保护模式,允许运行外部访问
protected-mode no
#-- 修改端口号
port 7000
#-- 启用后台进程模式,server启动后,不占用terminal窗口,也就是不会输出日志信息
daemonize yes
#-- 修改pid file的目录,pid是记录reids对应端口进程的id
pidfile /opt/redis-cluster/redis-5.0.5/cluster/7000/redis-7000.pid
#-- 修改持久化数据文件名称
dbfilename dump-7000.rdb
#-- 修改持久化数据文件保存路径
dir /opt/redis-cluster/redis-5.0.5/cluster/7000
#-- 配置内存回收机制(具体查看前面章节)
maxmemory-policy allkeys-lru
#-- 启动集群模式
cluster-enabled yes
#-- 设置集群配置文件
cluster-config-file nodes-7000.conf
# cp redis-7000.conf ../7001/redis-7001.conf
# cd ../7001
# vim redis-7001.conf
vim替换命令:
:1,$ s/7000/7001/g
将配置文件中 7000 替换成 7001
# ./src/redis-server cluster/7000/redis-7000.conf
# ps -ef | grep redis
# ./src/redis-cli --cluster create --cluster-replicas 2 192.168.31.52:7000 192.168.31.52:7001 192.168.31.52:7002 192.168.31.52:7003 192.168.31.52:7004 192.168.31.52:7005 192.168.31.52:7006 192.168.31.52:7007 192.168.31.52:7008
Can I set the above configuration? (type 'yes' to accept): yes
# ./src/redis-cli -p 7000
> info replication
[root@ddd redis-5.0.5]# ./src/redis-cli -p 7000
127.0.0.1:7000> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.31.52,port=7003,state=online,offset=182,lag=0
slave1:ip=192.168.31.52,port=7006,state=online,offset=182,lag=1
master_replid:fdf779a4eafa7581367e40fdb9557918b72260dd
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:182
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:182
#-- 查看redis 7000 端口的pid
# ps -ef | grep redis
#-- 停止pid进程
# kill -9
#-- 连接之前 7000 端口的从节点
# ./src/redis-cli -p 7003
[root@ddd redis-5.0.5]# ./src/redis-cli -p 7003
127.0.0.1:7003> info replication
# Replication
role:slave
master_host:192.168.31.52
master_port:7006
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:700
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:8d331c5876425ca511fd39a8421cbeee167c4123
master_replid2:fdf779a4eafa7581367e40fdb9557918b72260dd
master_repl_offset:700
second_repl_offset:505
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:700
将7000端口节点重新启动后,会自动加入到7006端口的主节点中
查看集群的槽分配
> cluster solt
问题1:哨兵模式下,客户端应该连接到那个 redis-server ?
public JedisSentinelPool(String masterName, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, int database, String clientName) {
......
HostAndPort master = this.initSentinels(sentinels, masterName);
this.initPool(master);
}
private HostAndPort initSentinels(Set<String> sentinels, String masterName) {
.......
//根据master名称获取对应的 host and port
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
.......
//监听master变化,如果master重新选举后,需要重新连接
while(var5.hasNext()) {
sentinel = (String)var5.next();
hap = HostAndPort.parseString(sentinel);
JedisSentinelPool.MasterListener masterListener = new JedisSentinelPool.MasterListener(masterName, hap.getHost(), hap.getPort());
masterListener.setDaemon(true);
this.masterListeners.add(masterListener);
masterListener.start();
}
.......
}
问题2:集群模式下,为什么会有MOVED的error ?
[root@ddd redis-5.0.5]# ./src/redis-cli -p 7000
127.0.0.1:7000> set name zcheng
(error) MOVED 5798 192.168.31.52:7001
[root@ddd redis-5.0.5]# ./src/redis-cli -c -p 7000
127.0.0.1:7000> set name zcheng
-> Redirected to slot [5798] located at 192.168.31.52:7001
OK
ok 前会提示,已经将 key 重定向到 7001 端口中,-c 表示如果key不能保存在当前节点,自动重定向
Jedis源码解释
入口
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
JedisCluster
找到:public BinaryJedisCluster(Set<HostAndPort> jedisClusterNode, int timeout, int maxAttempts, GenericObjectPoolConfig poolConfig) {
this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig, timeout);
......
}
JedisSlotBasedConnectionHandler()
找到:public JedisClusterConnectionHandler(Set<HostAndPort> nodes, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName, boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) {
.......
this.initializeSlotsCache(nodes, poolConfig, connectionTimeout, soTimeout, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
}
initializeSlotsCache()
找到: private void initializeSlotsCache(Set<HostAndPort> startNodes, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName, boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) {
.......
this.cache.discoverClusterNodesAndSlots(jedis);
.......
}
this.cache.discoverClusterNodesAndSlots
找到:public void discoverClusterNodesAndSlots(Jedis jedis) {
//上锁
this.w.lock();
try {
this.reset();
// 获取cluster的slot信息,为后面的key自动路由做准备
List<Object> slots = jedis.clusterSlots();
Iterator var3 = slots.iterator();
........
} finally {
this.w.unlock();
}
}
jedisCluster.set("name","123456");
set()
找到:public String set(final String key, final String value) {
return (String)(new JedisClusterCommand<String>(this.connectionHandler, this.maxAttempts) {
public String execute(Jedis connection) {
return connection.set(key, value);
}
}).run(key);
}
run()
找到:public T run(String key) {
// CRC16 计算 key 的 slot 值
return this.runWithRetries(JedisClusterCRC16.getSlot(key), this.maxAttempts, false, (JedisRedirectionException)null);
}
官网:https://redis.io/clients
jedis:使用socket进行命令操作
redission:分布式的、可扩展的 Java 数据结构,既可以通过命令操作,同时也实现了基于redis的功能,如分布式锁、队列、原子递增、全局ID
lettuce:基于netty构建的可伸缩的、线程安全的redis客户端,支持同步、异步和响应式
jedis(错误代码)
public class JedisClientDemo {
public static void main(String[] args) {
Set<HostAndPort> set = new HashSet<>();
set.add(new HostAndPort("192.168.31.52", 7006));
set.add(new HostAndPort("192.168.31.52", 7000));
set.add(new HostAndPort("192.168.31.52", 7003));
//HostAndPort hostAndPort = new HostAndPort("192.168.31.52", 7006);
JedisCluster jedisCluster = new JedisCluster(set);
jedisCluster.set("name","123456");
//JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(null,null);
}
}
public class RedissonClientDemo {
public static void main(String[] args) {
Config config = new Config();
config.useClusterServers().addNodeAddress("");
RedissonClient client = Redisson.create(config);
client.getBucket("").set("");
}
}
//TODO 将编写的代码上传到 github , jedis操作和redisson操作
用户请求数据时,先判断缓存数据,有则返回缓存数据,无则查询数据库的数据,如果有数据则回写到缓存
命中率
缓存雪崩:指缓存中大量的key的超时时间相同,导致同一时刻大量的key同时失效,从而可能导致同一时刻大量的请求直接访问到数据库,而如果这时数据库没有应对措施,就会导致数据库压力过大,使得数据库崩溃
如果redis宕机,停止运行?
缓存穿透:指客户端大量的请求都无法命中缓存的key,既有可能是恶意攻击的请求,导致redis性能下降,或者reids服务崩溃,导致数据库压力加重而崩溃
缓存 reids服务中key的标志(当前场景的),请求过来后,先经过布隆过滤器判断是否存在key,如果有才会将请求发送到redis服务
client --> bloom过滤器 --> redis服务 --> 数据库
布隆过滤器是一种空间效率非常高的概率性算法(压缩算法)
bitmap(位图)
int类型 4字节,32个比特位,既可存储32个十进制的数据
例如:存储5和3,对应的二进制是 101和11
假设有32个方格,表示32个比特位
5的存储是:从左到右,0开始数到4;既从第5个格子开始,将101分别写入3个方格
3的存储是:从左到右,0开始数到2;既从第3个格子开始,将11分别写入2个方格
既从高位到低位存储,其余空方格填 0
例如:正常存储 40亿的数据,一个数占4个比特位,则需要16G内存
原理:bitmap + hash映射
key 通过多个hash函数,每个函数得到的结果,对应bitmap中的一个方格,将 0 改为 1(标记法)(多个函数是为了降低有可能两个不同的key计算出相同值的概率,降低误判的概率)
既如果客户端发送请求到布隆过滤器,布隆过滤器将key进行过个hash函数计算,如果对应的函数结果,在bitmap上的值都是1,则认为key是存在的,否则,key不存在
实现:google的Guava、Redisson