计算机就是数据结构和算法.
redis的学习网站:
API代码的学习:
1 | 1. redis.io 的client 中有JAVA语言的客户端:jedis、lettuce等可以分别访问他们的github学习 |
☀基础概念
☀为什么要使用Redis
文件里 数据 data.txt,当查询某个字段的时候grep awk
,java但是随着文件变大,速度变慢为什么?
因为:硬盘i/o成为瓶颈。查询有一个过程,取出比较。主要取决于取出的速度。
💖常识:
磁盘:
寻址:ms
带宽:G/M
内存:
寻址:ns
带宽:很大
秒>毫秒>微秒>纳秒 磁盘比内存在寻址上慢了10W倍
I/O buffer:成本问题
磁盘与磁道,扇区,一扇区 512Byte带来一个成本变大:索引
windows操作系统,一个扇区4K,4K 操作系统,无论你读多少,都是最少4k从磁盘拿,4K对其。
😘关系型数据库特性
磁盘中:数据+索引,内存中B+T树。
关系型数据库建表:必须给出schema
类型:字节宽度。
存:倾向于行级存储。
(✿◡‿◡)面试题:
数据库:表很大,性能下降?
如果表有索引
维护索引会使得,增删改变慢。
查询速度呢?
1个或少量查询依然很快。
并发大的时候会受硬盘带宽影响速度。
💕技术选型
数据库对比网站:https://db-engines.com/en/
架构师:
- 技术选型
数据在磁盘和内存体积不一样,SAPHANA内存级别的关系型数据库2T,这种造价太高。
取折中的方案,使用缓存
memcached
redis
速度主要是由个基础设施影响:
冯诺依曼体系的硬件
以太网,tcp/ip的网络
☀Redis简介
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
😎Redis和memcache的区别
总结一句话:计算像数据移动。
☀字符集的的使用
字符集 ascii,其他一般叫做扩展字符集。
扩展: 其他字符集不在对ascii重编码。
0xxxxxxx,以0开头的肯定是ascii编码,其他字符集一遇到就默认使用ascii了。
你自己写一个程序,字节流读取,每字节判断。
☀Redis安装
😊命令执行过程:
1 | 1,yum install wget |
☀卸载Redis
find / -name redis
查找redis的文件目录,删除即可。
☀常用命令
☀查看Redis
查看Redis的bin被安装位置
1 | whereis redis-cli |
=======
测试结果,只会找配置在环境变量中的目录下的。
查看Redis的进程:
1 | ps -ef |grep redis |
查看Redis版本:
1 | ./redis-server -v |
☀登录Redis
1 | ./redis-cli -h IP地址 -p 端口 -a 密码 -c --raw |
参数 | 描述 |
---|---|
-c | 开启reidis cluster模式,连接redis cluster节点时候使用。 |
-p | 端口号,不写此参数就是用默认端口 |
-h | ip地址,不写,默认本机。 |
--raw | 使用客户端的指定的字符集进行解码显示。 |
☀启动Redis
1 | #如果在服务目录/etc/init.d/****中注册了脚本. |
参数 | 描述 |
---|---|
--loadmodule | 加载模块启动服务 |
☀停止Redis
强制停止法:
1 | ps -ef | grep redis #查看端口占用的进程 |
登录客户端停止法:
1 | # 登录命令 |
服务停止法:
1 | service redis_6379 stop |
☀查看Redis配置:
1 | redis-cli ... #登录redis |
☀conf配置文件
引入公共的配置:
加载模块配置
绑定能够访问的IP地址
默认是本机。如果需要在虚拟机外部的程序访问需要修改为:
1 | bind 0.0.0.0 |
是否允许外部主机访问。
是否为后台服务模式
启动后,进程的pid文件。放在这个目录下。通过pid文件判定进程是否在运行。
日志级别
日志存放在那个目录下:
库的数量:
需要密码登录:
将一些敏感的命令重命名,屏蔽。
允许最大连接数:
最大内存,根据经验一般是1G~10G。
☀内核演变过程
☀演变
🤢阻塞时期:
cpu只有1颗
JVM: 一个线程的成本 1MB
线程多了调度成本CPU浪费。
内存成本。
🤔非阻塞时期
同步非阻塞 NIO
如果有1000fd,代表用户进程轮询调用1000次kernel,成本问题。
😃select,多路复用NIO时期。
fd相关数据考来考去。数据拷贝和传参调用成本比较高。
😎epoll,多路复用NIO时期。
共享空间mmap,降低了传参调用。
😍🤷♀️mmap,sendfile时期。
也就是现在的卡夫卡(kafka)技术的出现。减少数据拷贝和传参调用。
☀Redis内核
redis的每个服务端都是,单进程,单线程,单实例。
单线程单进程的好处:“顺序”性,每连接内的命令顺序。
☀Value结构和操作命令:
客户端help
命令可以查看所有命令的用法。
☀字符串
redis所有存储的都是字节流。如果时字符串,取决于客户端的编码格式,所以一定要统一编码。
每一个字节,都存在正反向索引:
set
设置值。
get
获取key的值。
append
字符串追加。
setrange
从某个位置开始覆盖替换字符串。
getrange
获取一段字符串,从字节位置开始。所以用来取中文就不太方便了。
strlen
获取字节数量意义上的长度。
☀数值
incr
从原来的数上+1.
用途场景:
抢购,秒杀,详情页,点赞,评论
规避并发下,
对数据库的事务操作
完全由redis内存操作代替
☀bitmap
🤷♀️直接操作二进制位,牛逼。
setbit
针对某个二进制位进行设置(0/1):
1
2
3 setbit k1 1 1
setbit k1 7 1
setbit k1 9 1
bitcount
根据字节角标,计算二进制位
1
存在的数量。
bitpos
根据字节角标,命令返回字符串里面第一个被设置为1或者0的bit位。出现的起始位置坐标。
🐱👤注意:需要指定查询出现的0或者1。
BITPOS k1 0 0
第一个参数
0
指定查询0第二个参数是
0
指定从第0个字节角标开始。
bitop
将多个bitmap进行逻辑计算操作,并将结果存入新的bitmap中。
☀使用场景
有用户系统,统计用户登录天数,且窗口随机.
😏实现思路:用户id作为key,将二进制位
1
存储表示已登陆的实际第几天。1
2
3
4
5setbit sean 1 1
setbit sean 7 1
setbit sean 364 1
STRLEN sean
BITCOUNT sean -2 -1京东就是你们的,618做活动:送礼物,大库备货多少礼物?
假设京东有2E用户
僵尸用户
冷热用户/忠诚用户
活跃用户统计!随即窗口
比如说 1号~3号 连续登录要 去重
😏实现思路:天数作为key,二进制位代表实际用户,已登录,用
1
表示。1
2
3
4
5setbit 20190101 1 1
setbit 20190102 1 1
setbit 20190102 7 1
bitop or destkey 20190101 20190102
BITCOUNT destkey 0 -1
☀List
链表结构,存在争对元素的正负索引。
😁口诀:同向命令模拟栈操作,逆向命令模拟队列操作。
lpush
从左向右存入元素。
lpop
按从左向右存入的顺序,弹出最后存入的数据。
lpush
+lpop
同向命令模拟栈的使用。
rpush
从右向左存入元素
rpop
按从右向左存入的顺序,弹出最后存入的数据。
lpush
+rpop
逆向命令模拟队列的数据结构。
LRANGE
按照给定的范围,取出list中的值。
lpush
的list是栈取出。rpush
的list是队列取出。
LINDEX
按照索引取出值。操作索引就相当于数组结构了。
LSET
按照索引设置对于的某个值.
LREM
从list中,按照给定的数量,移除某个值.数量为正数,正向移除,为负数,逆向移除.
LINSERT
在list中的某个元素前后插入元素。
llen
统计有多少个元素。
blpop
brpop
阻塞弹出元素。实现消息的单播队列。谁先阻塞,谁先拿到元素。FIFO,先进先出。
开启窗口1 2 3 。
窗口1先进入阻塞,等待k1弹出。
窗口2后进入阻塞,等待k2弹出。
窗口3,压入元素。
窗口1,先收到弹出的元素。
窗口3,再压入元素。
窗口2,收到弹出的元素。
LTRIM
对两端外的数据进行移除和删除。不包括端的元素。
☀Hash
hset
存hash,键值对类型的数据。
hmset
在一个key的value中,存放多个,键值对类型数据。
hget
取出
hmget
根据key的values中根据多个键取值。
hkeys
取出所有的键。
hvals
取出所有的值。
hgetall
取出所有的键和值。
hincrbyfloat
将某个map中的值进行浮点数计算。
☀Set
最有优势的就是去重。
sadd
集合中添加元素.
smembers
显示集合中的元素.
sinter
做多个集合的交集,不存放结果.,通过io将结果输出.
sinterstore
做多个集合的交集,存放结果.没有io的交互.👓
sunion
做并集
sunionstore
做并集并保存.
sdiff
差集.以第一个元素为基础取差集。
srandmember
随机数
SRANDMEMBER key count
正数:取出一个去重的结果集(不能超过已有集),超过已有集数量返回整个set集合。
负数:取出一个带重复的结果集,一定满足你要的数量。
如果:0,不返回
🎑应用场景:
抽奖:
10个奖品
用户:
<10 使用负数抽取。
>10 使用正数抽取。
中奖:是否重复解决家庭争斗!
取名字的时候。5个名字,取10次(可重复)
spop
将集合元素随机弹出。每次只弹出一个。
☀sorted_set
理解排序的集合中,内部存储方式。对每个元素内置了一个分值,按照分值来排序的。
🎆**面试问题:**排序是怎么实现的,增删改查的速度?
答:使用了跳跃表的概念。吸收存储空间,换取未来的查询速度。
zadd
集合中添加元素。
zrange
取出集合中的元素,按照元素角标顺序。
zrangebyscore
通过分值范围取出元素。
zrevrange
逆向取出元素。从大到小取出。
zscore
通过元素取出分值。
zrank
通过元素,取出排名,就是索引角标。
zincrby
增加元素的分值,并且根据分值实时维护,元素的顺序。
🎗场景:
歌曲排行榜。倒叙显示。每播放一次+1.
zunionstore
并集操作。
参数numkeys表示参数运算的key。
weight权重,与对应集合中的分值相乘计算后,参数与最后的集合与几何间的运算。sum|min|max,集合元素的运算,默认为加法。
权重运算:
求最大值运算:
☀管道pipeline的使用
接住管道可以一次发送多个命令,节省往返时间。
建立socket链接
nc localhost 6379
将多个命令,通过管道传入redis执行。
1 echo -e "set k2 99\nincr k2\n get k2 | nc localhost 6379"一般用于冷加载,启动的时候,将一些预留的数据放到redis中。
☀发布订阅
pubsub
publish
向管道中添加元素。
SUBSCRIBE
接受元素,监听之后别人发的消息才能收到.
✌🤞**使用场景:**微信聊天窗口。
☀事务的使用
永远记住一句话,速度快时特征,才会去选用Redis。Redis设计的事务也是往速度方面设计考虑,不是特别复杂。
为什么 Redis 不支持回滚(roll back)
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。以下是这种做法的优点: Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
MULTI
开启事务
EXEC
关闭事务,谁的exe先到达,谁的事务就先执行。
WATCH
乐观锁。
例:
窗口1:watch k1 开启事务
窗口2:开启事务,修改k1,关闭执行事务。
窗口3:执行事务,由于k1被修改了,事务执行失败。
☀Module布隆过滤器的使用
增加Redis的功能。
☀安装bloom-filter
点开modules
访问RedisBloom的githubhttps://github.com/RedisBloom/RedisBloom
linux中wget *.zip
yum install unzip
unzip *.zip
make
cp bloom.so /opt/mashibing/redis5/
redis-server --loadmodule /usr/local/redis/redisbloom.so 。
🎨**注意:**此处启动,关联布隆过滤器的函数库redisbloom.so文件一定要用绝对路径。不能指定配置文件。才能启动成功。是属于临时启动。
☀命令
BF.ADD
布隆过滤器中添加元素。
BF.EXISTS
验证是否存在。
返回
1
存在,返回0
不存在。
CF.ADD
布谷鸟过滤器相关的命令。
☀解决穿透问题
概率解决问题,不可能百分百阻挡,>1%
1,你有啥
2,有的向bitmap中标记
3,请求的可能被误标记
4,但是,一定概率会大量减少放行:穿透
5,而且,成本低
☀几种分布式实现方式
第三种最优。最符合分布式架构的思想。
☀过滤器种类介绍
过滤器:
bloom
布隆过滤器
counting bloom
cukcoo
布谷鸟过滤器
☀穿透了,但是数据库不存在的处理
client,增加redis中的key,value标记.
数据库增加了元素.
完成元素对bloom的添加.
过滤器相当于是对数据库存量的一个小的映射。
☀Redis作为缓存
设置key的存活时长
1 set k1 ex 50 #设置k1存活时长为50秒.
ttl key
查看key剩余存活时长.
结果 描述 -2
表示key已经过期,或者不存在。 -1
表示key,一直存在。没有过期时间。 正数
表示key的剩余过期时间,单位秒。
expire
设置key的过期时间。单位秒。几秒后过期。
🎭注意:发生写会剔除过期时间。只针存在的key或者未过期的key有效。对已经过期,或者不存在的key设置无效。
🤗思考为什么会剔除过期时间?
设置新值的时候,新值存放在一个新的物理地址中,key指向新的value的物理地址,过期时间针对的是老的value。
expireat
设置key在将来的某个时间节点过期。
time
取时间戳.
☀回收策略
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
以下的策略是可用的:
- noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
☀过期淘汰原理:
Redis keys过期有两种方式:被动和主动方式。
当一些客户端尝试访问它时,key会被发现并主动的过期。
当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。
具体就是Redis每秒10次做的事情:
- 测试随机的20个keys进行相关过期检测。
- 删除所有已经过期的keys。
- 如果有多于25%的keys过期,重复步奏1.
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。
☀持久化
一个进程既要满足修改,又要满足持久化,成本很高。
Redis的存储层:
- 快照/副本
- 日志
☀RDB
☀RDB持久化可实现的几种方式:
☀阻塞,数据拷贝:
save命令实现
弊端:不能对外提供服务.明确关机维护的时候可以使用此命令。
☀非阻塞,数据拷贝:
弊端:时点混乱.不使用此方式。
☀非阻塞,fork
1.bgsave命令实现。
2.配置文件中给出bgsave的规则。
fork原理图:
☀优缺点:
☀😜优点:
时点性
类似java中的序列化 恢复的速度相对快
☀🤔缺点:
不支持拉链 只有一个dump.rdb
丢失数据相对多一些 时点与时点之间窗口数据容易丢失。
8得到一个rdb,9纲要洛一个rdb,挂机了
☀fork介绍
☀进程的概念
父子进程之间的关系:
进程之间是数据隔离的。
父进程其实可以让子进程看到数据,当刚创建还未修改时,本质是子进程中的指针与父进程中的一致。
子进程修改变量,不会影响父进程。父进程中修改变量也不会影响子进程。
创建子进程需要考虑的维度:
速度。
内存空间够不够。
fork()方法特点:
- 速度快。
- 占用内存小。
fork过程图解:
copy on write机制。
💖重点一句话:玩的都是指针。
☀RDB配置:
1 | save 900 1 #900秒内超过1条数据。 |
☀AOF
☀AOF概念:
redis的写操作记录到文件中.
☀优点:
丢失的数据量少。
☀AOF发展史
☀4.0以前
弊端:体量无限变大,恢复慢。
RDB和AOF可以同时开启,如果开启了AOF 只会用AOF恢复。
日志方案:hdfs,fsimage+edits.log 让日志只记录增量 合并的过程。
重写策略–删除抵消的命令 合并重复的命令。最终也是一个 纯指令的日志文件。
☀4.0以后
将老的数据RDB到aof文件中 将增量的以指令的方式 Append到AOF。
AOF是一个混合体 利用了RDB的快 利用了日志的全量。
重写命令执行之后,才会变成混合体。
增量日志+全量时点数据。恢复速度快,数据更全面。
☀AOF配置:
AOF记录时,写操作会触发IO。根据数据重要性,选择合适的写操作。
1 | appendonly yes #开启aof |
往磁盘写操作内部理解:
只要是写操作,都会调用系统内核,内核中间有个buffer,缓冲区。就是用来减少交互次数。提升效率。
☀实操
🙄配置文件相关:
设置redis,后台跑,还是前台阻塞跑。
1 | daemonize yes/no |
设置输出日志文件路径:
1 | logfile /var/log/redis_6379.log |
如果关闭了后台运行,日志也可以关闭,使其在控制台前台打印。
开启Aof追加模式
1 | appendonly yes |
aof-rdb混合开关
1 | aof-use-rdb-preamble no/yes |
😏命令相关:
1 | save #阻塞方式rdb保存数据 |
☀AFK原理
☀单机存在的问题
- 单点故障。
- 容量有限。
- 压力。
☀AFK策略图解
AKF
X轴:每一台都是全量,有一台为主,其他为镜像。
Y轴:通过业务和功能区分。分段。
Z轴:每种业务,优先级,逻辑再拆分。将数据落到不同的片上。分片。
☀AFK一变多,数据一致性解决方案,针对x轴。
通过AKF一变多
需要解决的问题:数据一致性!!!!
反问自己:为什么一变多,解决可用性。
同步阻塞法
强一致性。但是,如果阻塞时间过长,破坏可用性。
异步法
弱一致性,如果异步处理出现失败异常,会丢失数据。
kafka法
redis与kafka之间为同步阻塞。redis和kafka之间为异步。由于kafka的可靠性,一定会同步到其他redis中。
期间可能会取到不一致的数据。但是最终数据会一直。强调:强一致性。
☀主备、主从、主主概念区分:
参考博客链接:https://www.cnblogs.com/tankblog/p/11190598.html
主备 主从 主主模式
单点故障的情况不可避免,而且单副本的存储方案早已无法满足业务的可靠性要求,单机可靠性就就两个9,也就是一年大概有3.65天不可用。因此一般情况下我们至少也会上个双机存储架构。凡事最好有个plan B。
☀主备
主:主机,备:备机。
主机的意思当然是以它为主了,读写都是主机上,而备机呢就是备用,默默的在背后吸收主机的数据,时刻待命着等待主机挂了之后取而代之(没这么坏哈哈)。因此在主机还活着的情况下,备机的唯一使命就是同步主机的数据,不对外提供服务。
image
❀优点:简单,主备之间只有数据同步,不需要考虑别的情况。就很简单的配置一下,再搞一台服务器就能组成主备架构了。
❀缺点:备机等于就拿来备份,浪费了备机这台服务器的资源。上面说的不考虑别的情况指的是主机和备机它们两之间就只要复制数据.
但是有些情况我们人还是得考虑的:主机挂了如何让备机上。有三种选择:
人工切换。人工切换时效性不高,出了事情首先你得开机,登录远程一阵啪啪得好几分钟或者万一你在LOL,黑铁晋级青铜最后一把努力了几个月即将晋升倔强青铜的一刻!是吧。还要万一在深夜或者说…是吧。
引入中间件。例如ZooKeeper、keepalived。就跟好多房东把房子委托给中介一样,这中间件就是个中介。全权由中介来打理主机和备机,它会根据机子状态来判别这时候是不是该备机上了。(建议)
主机备机之间状态传输(咱不找中介了,自己来打理),啥意思呢?就是除数据同步,主备之间还要有个状态传输过程,来让备机只要现在主机过得好不好,可以是主机主动推送它的状态给备机,或者是备机去索要状态。当状态拿不到或者不对的时候就开始主备切换。但是可能传输出现了波动啥的,导致备机误判了,然后备机升级为主机,这样就两主机了(下面会说主主的问题)。
☀主从
主:主机,从:从机
从机和备机的区别在于它得除了同步数据之外还得干活,对外提供读的操作,你可以理解为它是仆从。但是仆从和备机一样也有翻身做主人的一天,所以它也在默默的等待着主机挂了,取而代之。image
❀优点:
充分利用了资源,嘿嘿不想备机这么爽了,还得出来干活,对外提供读操作。而且在主机挂了的时候,如果没任命新机主之前,读操作还是能用的。
❀缺点:
客户端需要多个判断,也就是不同操作需要发放给不同服务器,我上图主机提供读写,有时候读写分离了,主机就提供写。
主从延迟,读操作分配给从库,就会存在数据同步的延迟问题,比如某个人注册了账号之后,登录走的是从机,这时候数据还未从主机同步过来,那可不让人很难受了。有关主从延迟问题的一些解决办法
和主备一样的切换问题。(参考主备)
☀主主
主主就是两台都是主机。同时对外提供读写操作。客户端任意访问提供的一台。
image
❀优点:
主主的好处就是可以把写操作也分担一下,但是问题恰恰就出在写操作上,导致主主的架构有很大的局限性。
❀缺点:
例如主机A有个注册的插入操作,生成的id是50,同一时刻主机B也有个插入操作生成的id也是50。然后它们之间的数据同步了,你说是谁覆盖谁呢?谁覆盖谁都不对!
因此主主只适用于可以双向复制,覆盖的数据(例如用户登录生成的token)。但是我们平日里绝大部分的数据都不允许。
结语
这种双机存储架构一般而言应用于一些业务量不大的场景。主要还是为了存储的可用性。
☀CAP
☀CAP概念
参考链接:http://www.ruanyifeng.com/blog/2018/07/cap.html
分布式系统(distributed system)正变得越来越重要,大型网站几乎都是分布式的。
分布式系统的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。
本文介绍该定理。它其实很好懂,而且是显而易见的。下面的内容主要参考了 Michael Whittaker 的文章。
☀一、分布式系统的三个指标
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。
- Consistency
- Availability
- Partition tolerance
它们的第一个字母分别是 C、A、P。
Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 CAP 定理。
☀二、Partition tolerance
先看 Partition tolerance,中文叫做"分区容错"。
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。
上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。
☀三、Consistency
Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。
接下来,用户的读操作就会得到 v1。这就叫一致性。
问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。
这样的话,用户向 G2 发起读操作,也能得到 v1。
☀四、Availability
Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应。
用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
☀五、Consistency 和 Availability 的矛盾
一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
在什么场合,可用性高于一致性?
举例来说,发布一张网页到 CDN,多个服务器有这张网页的副本。后来发现一个错误,需要更新网页,这时只能每个服务器都更新一遍。
一般来说,网页的更新不是特别强调一致性。短时期内,一些用户拿到老版本,另一些用户拿到新版本,问题不会特别大。当然,所有人最终都会看到新版本。所以,这个场合就是可用性高于一致性。
实时证明,大多数都是牺牲了一致性。像12306还有淘宝网,就好比是你买火车票,本来你看到的是还有一张票,其实在这个时刻已经被买走了,你填好了信息准备买的时候发现系统提示你没票了。这就是牺牲了一致性。
但是不是说牺牲一致性一定是最好的。就好比mysql中的事务机制,张三给李四转了100块钱,这时候必须保证张三的账户上少了100,李四的账户多了100。因此需要数据的一致性,而且什么时候转钱都可以,也需要可用性。但是可以转钱失败是可以允许的,这时候舍弃可用性。
☀Redis中主从CAP监控。
Redis中的集群关系,采用主从
监控法,实行主备切换,由多个监控程序集群监控主。
监控集群,势力范围n/2+1,一般使用奇数台。
奇数和偶数允许风险的个数一样,但是发生风险的个数偶数大于奇数,所以选择奇数台。
☀Redis主从复制
使用经验:
- 从机如果是RDB模式,无论从机挂后重启或者从机运行时,每次同步数据,可从RDB文件中记录的ID给出偏移量,给到主,最后同步少量的增量。如果时AOF模式,每次同步数据都是全量同步。
- 同步数据时从节点会先删除老数据,再同步新数据。
☀开启主从复制:
☀使用命令的方式:
❀redis运行时
SLAVEOF
REPLICAOF host port
redis5版本后可以使用。
❀redis启动时:
不仅设置了主,还设置了AOF持久化的方式。
redis-server ./6380.conf --replicaof 127.0.0.1 6379 --appendonly yes
个人理解--参数
相当于覆盖原有的配置文件中的参数。
☀通过配置文件方式:
设置链接主redis的ip和端口:
1 | replicaof <masterip> <masterport> |
设置链接主redis的参数:
1 | masterauth <master-password> |
设置下载主最新rdb时,老数据是否可读。
1 | replica-serve-stale-data yes |
设置从只读
1 | replica-read-only yes |
设置同步方式,间接通过磁盘还是,直接网络IO。如果磁盘速度很慢,网络带宽很快就直接通过网络IO。
1 | repl-diskless-sync no |
设置在同步增量数据时的存放队列的最大容量。如果超出最大容量,则直接使用全量RDB同步。
1 | repl-backlog-size 1mb |
#增量复制,至少写成功三条。主从异步同步数据的弱一致性,往强一致性上靠。
1 | min-replicas-to-write 3 |
☀手工切换主备
1 | `REPLICAOF NO ONE` #设置替换的从机不再追随。 |
☀监控哨兵Sentinel
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
虽然 Redis Sentinel 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器, 你可以在启动一个普通 Redis 服务器时通过给定 –sentinel 选项来启动 Redis Sentinel 。
☀启动哨兵
哨兵可以独立成一个进程,一个程序。也可以使redis服务进程包含哨兵这段代码。
独立进场方式:
1 | redis-sentienl <配置文件> |
redis服务方式:
1 | redis-server <配置文件> --sentinel |
配置文件参考目录:下载的源码目录下。/usr/local/redis/redis-5.0.12
1 | port 26379 #配置哨兵端口号 |
☀Spring整合Redis
官网学习:https://spring.io/projects/spring-data-redis
☀Redis的Sharing分片逻辑
akf主要解决单机问题:
- 单点故障.
- 容量问题.
- 访问压力.
x,y,z轴思想.
主从复制,HA :从X轴,生成数据镜像,解决单点故障.
单节点:容量的问题,采取Sharing分片.
☀Y轴拆分方式
y轴按逻辑: 业务拆分
☀Z轴Sharding分片逻辑:
- 按算法:hash+取模modula
缺点:取模的数必须固定 %3 %4 %10,影响分布式下的扩展性,每次添加新节点必须全局洗牌.
- 逻辑:random 随机
random算法,用于存放list数据.例如实现消息队列场景.
逻辑:kemata 一致性哈希,没有取模data,node
将node参数一致性hash运算,挂在一个环形的hash环上(实际是挂在树结构的节点上).每次有新的key进来,参数一致性hash运算,按照大小找到最近的node,存入机器.
将node物理节点的实际ip计算,换成ip+数字,造出多个代表实际主机的虚拟节点,挂在哈希环上,解决数据倾斜问题.
❀优点:
你加节点,的确可以分担其他节点的压力,不会造成全局洗牌
❀缺点:
新增节点造成一小部分数据不能命中
1,问题,击穿,压到mysql
2,方案:没去取离我最近的2个物理节点
更倾向于 作为缓存,而不是数据库用!!!!
☀Redis代理原理
代理可以解耦后面的Redis复杂度.
☀客户端直连Redis场景:
redis 的连接成本很高 对server端造成的,客户端直连,在很多客户端的情况下,会造成连接数过多,增加了链接成本.三次握手消耗资源.
☀使用Proxy代理链接Redis场景:
达到负载均衡,减少链接的压力.
单台代理主机,可能访问量大的时候,proxy主机压力过大.
☀使用虚拟服务LVS,和keepalived监控,负载均衡代理Proxy
LVS,虚拟出一个VIP地址.
keepalived,监控LVS,使LVS高可用.监控proxy代理.
☀代理层的实现
Redis分片逻辑在代理层实现:
代理技术:
twemproxy
github地址:https://github.com/twitter/twemproxy
predixy
cluster(Redis自身)
codis
☀Redis中的cluster分片技术
☀预分区技术
由于三种分片算法,modula,random,ketama,一旦集群增加主机后,都会出现找不到原有数据情况.所以只能用作缓存.不能用作数据库.
实际分区两个,取模运算时,使用预分区个数10计算,算出的结果,在映射mapping下,转到实际主机.
w
当集群中增加redis时,将mapping映射关系和对应数据实际分区同步转移到,新增的主机中.
redis自身集群中.每台都会带有一个hash算法,和映射mapping,客户端随机访问主机,都会跳转到正确的redis机器上.
数据分治聚合操作很难实现事务
☀Redis集群搭建
☀twemproxy
github地址:https://github.com/twitter/twemproxy
简单安装搭建步骤:
下载编译
1
2
3
4
5
6git clone git@github.com:twitter/twemproxy.git
cd twemproxy
autoreconf -fvi
./configure --enable-debug=full
make
src/nutcracker -h安装为服务
1
2
3
4
5
6
7
8
9
10
11
12cp nutcracker.init /etc/init.d/twemproxy #将源码路径script文件夹下的nutcracker.init,放入服务脚本文件夹中.nutcracker.init内容可研究参考.
cd /etc
mkdir nutcracker #创建脚本指定的启动配置文件夹.
cp /usr/local/twemproxy/conf/* /etc/nutcracker/ #复制源码目录中的启动配置文件,进脚本指定的启动配置文件夹中.
cp nutcracker /usr/bin/ #拷贝源码src目录下的nutcracker脚本到环境变量的目录中.
service twemproxy start #启用代理服务.
缺点:
不支持
keys *
.不支持
watch
,multi
事务.
☀predixy代理
github地址:https://github.com/joyieldInc/predixy
predixy评测:https://blog.csdn.net/rebaic/article/details/76384028
特点:
- 可以监控一套主从复制,也可以监控多套主从复制.
- 可以支持事务,但是只支持单group.
简易搭建步骤:
☀下载
地址:https://github.com/joyieldInc/predixy/releases
解压后,修改conf目录下的predixy.conf配置文件:
解开端口号:
☀哨兵sentinel代理配置:
通过哨兵发现并代理Redis.
1.引入哨兵配置:
只能引入一种:
2.配置主从哨兵:
sentinel.conf
Sentinels中配置哨兵的集群.
❀两套主从:
Group为哨兵监控的主从组.名称为,哨兵配置文件中的主从关系名称.
缺点是不支持事务.
❀:一套主从
只需要设置一个group就可以了.
优点,支持事务
3.启动predixy:
进入bin目录:
1 | ./predixy ../conf/predixy.conf #执行脚本,指定对应的配置文件. |
☀Cluster集群
教程:http://redis.cn/topics/cluster-tutorial.html
Redis 集群的数据分片
Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
- 节点 A 包含 0 到 5500号哈希槽.
- 节点 B 包含5501 到 11000 号哈希槽.
- 节点 C 包含11001 到 16384号哈希槽.
这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
☀单机创建集群演示
create-cluster:
脚本目录:/usr/local/redis/redis-5.0.12/utils/create-cluster
修改create-cluster脚本中的配置
1
2
3
4
5# Settings
PORT=30000
TIMEOUT=2000
NODES=6 #总结点数。
REPLICAS=1 #每个分片的从节点数。 分片数=(6/1+1)启动所有节点实例
1
./create-cluster start
创建分片和主从关系。
1
./create-cluster create
链接集群:
1
redis-cli -c -p 30001
停止集群:
1
./create-cluster stop
清除日志,文件数据等缓存
1
./create-cluster clean
☀分布式创建集群演示
使用客户端命令redis-cli
创建.
查询帮助:
1 | redis-cli --cluster help |
启动所有redis实例
集群的redis配置文件添加:
1
2
3
4
5port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes客户端命令,创建集群分片和主从关系:
1
redis-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas 1
迁移数据
解决新增、减少节点的问题,解决数据倾斜的问题。1
redis-cli --cluster reshard 127.0.0.1:30001 #迁移节点上的数据。127.0.0.1:30001表示连接的集群地址,不代表要迁移的节点。
查看集群节点信息
1
redis-cli --cluster info 127.0.0.1:30001
1 | redis-cli --cluster check 127.0.0.1:30001 |
☀事务的使用技巧
分布式同一个事务中的不同key操作,如果计算出key的槽在不同节点,那么事务就无法提交。
解决方法:将事务关联的key,都设置{oo}类似一样的前缀,那么计算出的槽位,会在相同的节点下,这时候事务就可以提交执行。
☀击穿
击穿发生场景:某个的key已经过期,在高并发情况下访问。造成直接到达访问数据库。通俗理解为.某个key有几千的请求。
❀击穿解决方案:
当客户端访问key为null的时候,使用setnx锁。只有获取到锁的客户端才能访问数据库。其他客户端没有得到锁的情况下不能访问数据库,只能停留等待,设置睡眠时间重复获取。
如果设置了锁,可能会导致死锁。
死锁解决方案:为锁设定过期时间。
如果设置了过期时间,可能会导致锁超时。
锁超时解决方案:锁超时意味着,访问数据库时间超过了锁的过期时间。这时候可以加一个监控线程,监控数据是否被取回来了,如果没有,更新锁的过期时间。
个人理解:做锁在一定程度上,降低了执行速度,但是提高了数据准确性。同时也可以间接降低服务访问压力。
☀穿透
穿透出现的场景:客户端访问系统中根本不存在的数据。
❀解决方案:
使用布隆过滤器:
- client包含布隆算法和数据。
- client包含了算法,数据在redis的bitmap中。redis无状态。
- redis集成了布隆算法和数据(布隆过滤器)。
布隆过滤器问题:不能删除数据。解决方法:1.使用布谷鸟。2.设置空key。
☀雪崩
大量的key同时失效,通俗理解为几百个key同时失效,每个可以有几十个几百个请求。
❀解决方案:
1.对于与时点性无关的数据,设置key的随机过期时间。
2.对与时点性有关的数据,比如零点必须统一过期的数据,采取强依赖的击穿方案。并且可以在业务层加入判断,零点延时,减缓访问压力。
☀分布式锁
setnx
已经做锁了,对于效率要求就不大了。对于准确度和一致性要求高。
redisson
锁的一种技术。
zookeeper
zookeeper没有redis快。数据可靠性、准确。zookeeper做分布式锁比较好。
☀API
官方文档:
https://docs.spring.io/spring-data/redis/docs/2.5.0/reference/html/#reference
☀高阶API
RedisTemplate
存放key的时候,会对key进行java字符串序列化.
StringRedisTemplate
存放key字符串就不会进行java字符串序列化操作了。
代码示例:
1 | package com.msb.spring.redis.demo; |
springboot配置文件:
1 | spring.redis.host=192.168.57.129 |
springboot启动测试类:
1 |
|
☀低阶API
RedisConnection
示例代码:
1 | RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); |
☀Hash实现
☀设置单个属性:
1 | public void hashTest() { |
☀存java对象。
引入pom工具:
1 | <dependency> |
实体类对象:
1 | public class Person { |
实现
1 |
|
☀自定义Template
自定义的Bean生成。
1 |
|
注入干预:
1 |
|
☀发布订阅实现
1 | public void pubandsubTest() {/*往一个通道发送消息。*/ |