Reids
Redis是什么
Redis是完全开源免费的,遵守BSD协议,是一个高性能(NOSQL)的key-value数据库
基于内存,用C语言开发,编译依赖gcc环境
Redis的设计与实现
高效的数据结构
string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
多路复用IO模型
用一个线程将这一万个建立成功的链接陆续的放入 event_poll,event_poll 会为这一万个长连接注册回调函数,当某一个长连接准备就绪后(建立建立成功、数据读取完成等),就会通过回调函数写入到 event_poll 的就绪队列 rdlist 中,这样这个单线程就可以通过读取 rdlist 获取到需要的数据。
需要注意的是,除了异步 IO 外,其它的 I/O 模型其实都可以归类为阻塞式 I/O 模型,不同的是像阻塞式 I/O 模型在第一阶段读取数据的时候,如果此时数据未准备就绪需要阻塞,在第二阶段数据准备就绪后需要将数据从内核态复制到用户态这一步也是阻塞的。而多路复用 IO 模型在第一阶段是不阻塞的,只会在第二阶段阻塞。
事件机制
Redis 客户端与 Redis 服务端建立连接,发送命令,Redis 服务器响应命令都是需要通过事件机制来做的
reactor模式
reactor模式配合一个队列,用一个serverAccept线程来处理建立请求链接,并且通过IO多路复用模型,让内核来监听这些socket,一旦某些 socket 的读写事件准备就绪后就对应的事件压入队列中,然后 worker工作,由文件事件分派器从中获取事件交于对应的处理器去执行,当某个事件执行完成后文件事件分派器才会从队列中获取下一个事件进行处理。
可以类比在 netty 中,我们一般会设置 bossGroup 和 workerGroup 默认情况下 bossGroup 为 1,workerGroup = 2 * cpu 数量,这样可以由多个线程来处理读写就绪的事件,但是其中不能有比较耗时的操作如果有的话需要将其放入线程池中,不然会降低其吐吞量。在 Redis 中我们可以看做这二者的值都是 1。
为什么说存储的值不易过大
比如一个 string key = a,存储了 500MB,首先读取事件压入队列中,文件事件分派器从中获取到后,交于命令请求处理器处理,此处就涉及到从磁盘中加载 500MB。
比如是普通的 SSD 硬盘,读取速度 200MB/S,那么需要 2.5S 的读取时间,在内存中读取数据比较快比如 DDR4 中 50G/秒,读取 500MB 需要 100 毫秒左右。
线程的库一般默认 10 毫秒就算慢查询了,大部分的指令执行时间都是微秒级别,此时其它 socket 所有的请求都将处于等待过程中,就会导致阻塞了 100 毫秒,同时又会占用较大的带宽导致吞吐量进一步下降。
Redis为什么使用
高可用
High performance 对数据库高并发读写的需求
主要原因性能极高和高并发(Redis能读的速度是110000次/s,写的速度是81000次/s 。)
如果只是为了分布式锁(还可以用zookeeper)
HUge storage 对数据库的高效率存储和访问的需求
减轻数据的访问量
数据库时IO操作,Redis是内存操作,效率比IO数据库高
High Scalability && High Availability 对数据库的高可扩展性和高可用的需求
性能极高
Redis能读的速度是110000次/s,写的速度是81000次/s
丰富的特性
Redis还支持 publish/subscribe, 通知, key过期等等特性。
原子性
Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行
丰富的数据类型
Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作
高速读写
Redis使用自己的分离器,代码量很少,没有使用lcok,效率非常高
Nosql
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题
键值(key-value)存储数据库
相关产品:Tokyo Cabinet/Tyrant、Redis、Voldmort、Berkeley DB
优势:快速查询
劣势:存储的数据缺少结构化
列存储数据库
文档型数据库
图形(Graph)数据库
总结
数据模型比较简单
需要灵活性更强的IT系统
对数据库性能要求比较高
不需要高度的数据一致性
对于给定的key,比较容易映射负责的环境
Redis安装
安装gcc
yum -y install gcc automake autoconf libtool make
注意:运行yum是出现/var/run/yum.pid已被锁定,PID为xxxx的另一个程序正在运行的问题解决
rm -f /var/run/yum.pid
安装redis
wget
cd /home/redis-5.0.8/
make
make不成功的情况
cd /home/redis-5.0.8/deps
make lua hiredis linenise
make PREFIX=/usr/local/redis install
启动
cd /usr/local/redis/bin/
./redis-server
./redis-cli
配置
cp /home/redis-5.0.8/redis.conf /usr/local/redis/
daemonize no 是否启动守护线程yes
bind 127.0.0.1 注释运行服务器外的机器访问redis服务
requirepass 设置密码
pidfile /var/run/redis_6379.pid
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
指定存储到本地是否压缩数据,默认为yes,Redis采用LZF(压缩算法)压缩。
dbfilename dump.rdb
dir ./
maxmemory <bytes>
Redis关闭
启动
./bin/redis-server ./redis.conf
意外关闭
断电、非正常关机
kill -9 强制关闭
客户端正常退出
shutdown
单线程工作模型为什么这么快?
纯内存操作
单线程操作,避免频繁的上下文切换
采用非阻塞I/O多路复用机制
Redis的数据类型及使用场景
缓存
排行榜
计数器
分布式会话
分布式锁(要考虑如何释放锁资源)
社交网络
最新列表
消息系统
验证码(短信)时间有效期
token
可以解决分布式数据同步
session功效解决方案
Redis支持的数据类型详解及具体应用场景
String
get/set
可以做一些比较复杂的计数功能
存储对象用String转换json存储
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
Hash
结构化对象,方便操作其中某个字段
单点登陆,以cookieId作为key,设置30分钟为缓存过期时间,模拟类似session的效果
就也是个生产者和消费者的场景,list可以很好的完成排队,先进先出的原则。
List
简单的消息队列
lrange做Redis分页功能,性能好
Set(不重复集合)
全局去重
交集、并集、差集做共同喜好,全部的喜好,自己独有的喜好
考虑为什么不用JVM自带的Set进行去重?
系统一般都是集群部署,使用JVM自带的Set,比较麻烦,
难道为了一个做一个全局去重,再起一个公共服务
Redis的Set是string类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
Sorted Set(zset)
权重Score,根据Score进行排列
排行榜应用
取TOP N操作
延时任务
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
Redis的过期策略和内存淘汰机制
定时删除
redis采用的是定期删除+惰性删除的策略
为什么不直接用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。
虽然内存及时释放,但是十分消耗CPU资源。
在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略
定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。
需要说明的是,redis不是每个100ms将所有的key检查一次,
而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。
因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。
也就是说在你获取某个key的时候,redis会检查一下,
这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,
也就是说惰性删除也没生效。这样,redis的内存会越来越高。
那么就应该采用内存淘汰机制。
LRU算法动态数据删除maxmemory-policy volatile-lru
该配置就是配内存淘汰策略的
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites);
那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本 上一致
Reids和数据库
双写一致性问题
最终一致性(放入缓存只能保证数据最终一致性)
强一致性
只能降低不一致的发生概率,有强一致要求的数据不能放入缓存
首先、采取正确的更新策略,先更新数据再删除缓存
其次、存在删除缓存失败的问题,提供一个补偿措施,例如消息队列
值不同步如何解决
清空缓存,在同步数据库中的值,重新缓存到redis
Redis缓存
缓存穿透
黑客故意取请求缓存中不存在的数据,导致所有请求都到了数据库上,从而导致数据库连接异常
用户查询数据库中没有的数据,数据库中没有缓存中也没有
用户查询的时候先到缓存中查找,在去数据中查找(相当于俩次无用的查询)
这样请求绕过缓存直接查找数据库,就是经常提的缓存命中率问题
解决方案
布隆过滤器(能迅速判断请求是否有效的拦截机制)
将所有可能存在的数据哈希到一个足够大的bitmap(哈希表)中
一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
内部维护一系列合法有效的 Key,迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回
Bloom Filter
就是引入了k(k>1)k(k>1)个相互独立的哈希函数,,保证在给定的空间、误判率下,完成元素判重的过程。
算法的【核心思想】就是利用多个不同的Hash函数来解决“冲突”。
利用互斥锁
缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
异步更新策略
无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,
异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
时间
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
缓存击穿
指一个key非常热点,大并发集中对这个key进行访问,当这个key在失效的瞬间,
仍然持续的大并发访问就穿破缓存,转而直接请求数据库。
解决方案
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key
来锁住当前key的访问,访问结束再删除该短期key。
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。
解决思路:
直接写个缓存刷新页面,上线时手工操作下;
数据量不大,可以在项目启动的时候自动进行加载;
定时刷新缓存;
缓存雪崩
原有缓存同一时间大面积的失效,新缓存未到期间
这个时候又来了一波请求,结果请求都怼到数据库上
从而导致数据库连接异常,对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,系统崩溃
解决方案
(加锁)使用互斥锁,但是该方案吞吐量明显下降了
(队列)的方式保证来保证不会有大量的线程对数据库一次性进行读写
(失效时间)给缓存的失效时间,加上一个随机值,避免集体失效。
(双缓存)我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。
然后细分以下几个小点:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。
缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
解决方案
定时去清理过期的缓存;
当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)
或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,
即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。
因此,对于不重要的缓存数据,可以采取服务降级策略,
例如一个比较常见的做法就是
Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
Redis 的并发竞争 Key 问题
用 Redis 事务机制(Redis 集群环境下,做了数据分片操作、key不一定在统一redis-server上)
使用分布式锁
对key的操作要求有顺序(写入数据时保存一个时间戳)
利用队列,将set方法变成串行化访问
Redis 持久化机制
数据恢复、高可用
什么是持久化
将数据存放(同步)到硬盘
Redis
数据是存放在内存中(默认开启了持久化机制、rdb存储到硬盘)
重启后通过硬盘文件重新加载到内存
实现
单独创建fork()一个子进程
将当前父进程的数据库数据复制到子进程的内存中
然后由子进程写入到临时文件中,持久化的过程结束了,
再用这个临时文件替换上次的快照文件
然后子进程退出,内存释放。
RDB(Redis DataBase)
不是实时存储,体积小
Redis默认的持久化方式
二进制文件(稳定方式、Snapshot快照存储
对应产生的数据文件为dump.rdb
通过配置文件中的save参数来定义快照的周期
功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
redis宕机后,redis值会失效吗?
不会,默认开启rdb存储
注意rdb存储方式在规定时间内key和valuy达到一定次数才开始做数据持久化
rdb存储方式断开连接,会自动备份,kill -9,断电除外
可以选择aof持久化方式,aof实时存储
AOF(Append-only file)
appendonly yes 开启
实时存储,日志方式存储,安全,体积较大
Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。
当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
aof写入保存:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
快照
可以是其所表示的数据的一个副本
也可以是数据的一个复制品。
存储结构:
内容是redis通讯协议(RESP )格式的命令文本存储。
比较:
1、aof文件比rdb更新频率高,优先使用aof还原数据。
2、aof比rdb更安全也更大
3、rdb性能比aof好
4、如果两个都配了优先加载AOF
15.Redis通讯协议(RESP )
RESP 是redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
For Simple Strings the first byte of the reply is "+" 回复
For Errors the first byte of the reply is "-" 错误
For Integers the first byte of the reply is ":" 整数
For Bulk Strings the first byte of the reply is "$" 字符串
For Arrays the first byte of the reply is "*" 数组
16.Redis 主重复制(读写分离、备份、高可用、宕机容错机制、集群)
一主(master、读写)多从(slave、只读)
从服务器会(实时)同步主服务器(快照文件)
修改slave从redis中的 redis.conf文件
slaveof 192.168.33.130 6379
masterauth 123456--- 主redis服务器配置了密码,则需要配置
同一时刻只会有一个主master
17.Redis 哨兵机制(sentinel)(高可用、监听作用)(26379)
哨兵机制关闭和redis没有关系,可以部署到不同服务器上
通过用投票选举新的Master(leader)
使用 keepalived监听、自动重启
监控(Monitoring)
提醒(Notification)
自动故障迁移(Automatic failover):
sentinel monitor mymast 192.168.110.133 6379 1 #主节点 名称 IP 端口号 选举次数
修改心跳检测 5000毫秒
18.Redis 事务 multi开启事务 esec提交事务
同时有多个子系统去set一个key。
这个时候要注意什么呢? 不推荐使用redis的事务机制。
因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。
你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。
因此,redis的事务机制,十分鸡肋。
19.Redis 容灾
20.热点数据和冷数据是什么(收藏数、点赞数、分享数;导航、寿星列表等)
热点数据,缓存才有价值
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存
对于上面两个例子,寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的场景。
对于热点数据
比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次
再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。
**数据更新前至少读取两次,**缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?
有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,
比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,
但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
21.Redis 内部结构
dict 本质上是为了解决算法中的查找问题(Searching)是一个用于维护key和value映射关系的数据结构,与很多语言中的Map或dictionary类似。 本质上是为了解决算法中的查找问题(Searching)
sds sds就等同于char * 它可以存储任意二进制数据,不能像C语言字符串那样以字符’\0’来标识字符串的结 束,因此它必然有个长度字段。
skiplist (跳跃表) 跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现,
quicklist
ziplist 压缩表 ziplist是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,
22.Redis为什么是单线程的
官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,
Redis的瓶颈最有可能是机器内存的大小或者网络带宽。
既然单线程容易实现,而且CPU不会成为瓶颈,
那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)
Redis利用队列技术将并发访问变为串行访问
1)绝大部分请求是纯内存操作(非常快速)
2)采用单线程,避免了不必要的上下文切话和竞争条件
3)非阻塞IO的优点
1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
2. 支持丰富数据类型,支持string,list,set,sorted set,hash
3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
4. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题
同时有多个子系统去set一个key。这个时候要注意什么呢? 不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。
(1)如果对这个key操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可
(2)如果对这个key操作,要求顺序: 分布式锁+时间戳。 假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
(3) 利用队列,将set方法变成串行访问也可以redis遇到高并发,如果保证读写key的一致性
对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。
23.对于大量的请求怎么样处理
redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;
redis是通过IO多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求的
24.Redis Cluster集群
1.twemproxy(代理)
大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy,
它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。
缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
特点:
1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
2、支持失败节点自动删除
3、后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致
缺点
增加了新的 proxy,需要维护其高可用。
2.failover(直连型)
逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预
2.codis
目前用的最多的集群方案,基本和 twemproxy 一致的效果
但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点
3.Redis Cluster集群
是Redis官方在3.0版本推出得一套分布式存储方案
完全去中心化,有多个节点组成,所有节点彼此互联(不存在哪个节点影响性能瓶颈)
Redis客户端可以直接连接任何一个节点获取集群中得键值对(不需要中间代理proxy)
如果该节点不存在用户所指定的键值,其内部会自动把客户端重定向到键值所在的节点。
数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
Redis 集群是一个网状结构
每个节点都通过 TCP 连接跟其他每个节点连接。
在一个有 N 个节点的集群中,每个节点都有 N-1 个流出的 TCP 连接,
和 N-1 个流入的连接,这些 TCP 连接会永久保持。
Redis Cluster 同其他分布式存储系统一样,主要具备以下两个功能:
数据分区
Redis 集群会将用户数据分散保存至各个节点中,突破单机 Redis 内存最大存储容量。
集群引入了 哈希槽slot的概念,其搭建完成后会生 16384 个哈希槽slot,同时会根据节点的数量大致均等的将 16384 个哈希槽映射到不同的节点上。
当用户存储key-value时,集群会先对key进行 CRC16 校验然后对 16384 取模来决定key-value放置哪个槽,从而实现自动分割数据到不同的节点上。
数据冗余
Redis 集群支持主从复制和故障恢复。集群使用了主从复制模型,每个主节点master应至少有一个从节点slave。
假设某个主节点故障,其所有子节点会广播一个数据包给集群里的其他主节点来请求选票,一旦某个从节点收到了大多数主节点的回应,
那么它就赢得了选举,被推选为主节点,负责处理之前旧的主节点负责的哈希槽。
依据 Redis Cluster 内部故障转移实现原理,Redis 集群至少需要 3 个主节点,而每个主节点至少有 1 从节点,
因此搭建一个集群至少包含 6 个节点,三主三从,并且分别部署在不同机器上。
bind 192.168.83.128 # 设置当前节点主机地址
port 7001 # 设置客户端连接监听端口
pidfile /var/run/redis_7001.pid # 设置 Redis 实例 pid 文件
daemonize yes # 以守护进程运行 Redis 实例
cluster-enabled yes # 启用集群模式
cluster-node-timeout 15000 # 设置当前节点连接超时毫秒数
cluster-config-file nodes-7001.conf # 设置当前节点集群配置文件路径
ps -ef | grep redis
节点握手
cluster meet ip port
cluster nodes
分配槽位
cluster info 命令来查看目前集群的运行状态。
cluster_state:fail表示当前集群处于下线状态。
cluster addslots 命令手动将 16384 个哈希槽大致均等分配给主节点 A、B、C。
cluster addslots {0..5461}
cluster_state:ok证明 Redis 集群成功上线。
主从复制
cluster replicate node-id 命令手动给从节点配置主节点。
集群中各个节点的node-id可以用cluster nodes命令查看,如下输出
自动方式搭建
使用 redis-trib.rb 搭建,不过之前需要安装 Ruby 环境。
yum -y install ruby ruby-devel rubygems rpm-build
ruby -v
/usr/local/bin/redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 --cluster-replicas 1
Redis与Memcache的区别都有哪些?
1)Memcache内存、Redis 内存和硬盘
2)Memcache字符、Redis(string、set、zset、hash、list)
3)底层模型不同、客户端之间通信的应用协议不同 Redis之间构建自己的VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4)value值大小不同Redis 最大可以达到 512M、Memcache只有 1mb。
5)redis的速度比memcached快很多
6)Redis支持数据的备份,即master-slave模式的数据备份。
Redis发布订阅(类似消息中间件)
含义
发布频道(生成者)
创建频道(主题)
关注频道(消费者、订阅者)
SUNSCRIBE 名称
Redis消息队列
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
redisTemplate.opsForList().leftPush(key, map);
redisTemplate.opsForList().rightPop(RedisKey.orderImportKey, 1, TimeUnit.SECONDS)
缺点:
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
Redis 分布式解决方案
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
Redis 客户端
Redis 高级功能
Redis开发运维常用问题探讨
Redis 复制的原理和优化策略
Redis安全 挖矿程序入侵(xmrig-notls=> 门罗币挖矿病毒) 转移字符 \ 民命文件,重新mv文件夹
Redis不建议使用
1)redis中也不建议使用keys命令
2)redis pool的配置应该合理配上(无错误日志,无报错,定位相当困难)
3)使用 stringRedisTemplate.getConnectionFactory().getConnection() 是不被推荐的
stringRedisTemplate.execute(new RedisCallback<Cursor>() {
@Override
public Cursor doInRedis(RedisConnection connection) throws DataAccessException {
return connection.scan(options);
}
})
或者使用完connection后 ,用RedisConnectionUtils.releaseConnection(conn, factory);来释放connection.
4)搭架服务器集群使用root权限关闭防火墙
(5)Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(6)如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(7) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内
(8) 尽量避免在压力很大的主库上增加从库
(9) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3…
Redis常用命令
Keys pattern
*表示区配所有
以bit开头的
查看Exists key是否存在
Set
设置 key 对应的值为 string 类型的 value。
setnx
设置 key 对应的值为 string 类型的 value。如果 key 已经存在,返回 0,nx 是 not exist 的意思。
删除某个key
第一次返回1 删除了 第二次返回0
Expire 设置过期时间(单位秒)
TTL查看剩下多少时间
返回负数则key失效,key不存在了
Setex
设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。
Mset
一次设置多个 key 的值,成功返回 ok 表示所有的值都设置了,失败返回 0 表示没有任何值被设置。
Getset
设置 key 的值,并返回 key 的旧值。
Mget
一次获取多个 key 的值,如果对应 key 不存在,则对应返回 nil。
Incr
对 key 的值做加加操作,并返回新的值。注意 incr 一个不是 int 的 value 会返回错误,incr 一个不存在的 key,则设置 key 为 1
incrby
同 incr 类似,加指定值 ,key 不存在时候会设置 key,并认为原来的 value 是 0
Decr
对 key 的值做的是减减操作,decr 一个不存在 key,则设置 key 为-1
Decrby
同 decr,减指定值。
Append
给指定 key 的字符串值追加 value,返回新字符串值的长度。
Strlen
取指定 key 的 value 值的长度。
persist xxx(取消过期时间)
选择数据库(0-15库)
Select 0 //选择数据库
move age 1//把age 移动到1库
Randomkey随机返回一个key
Rename重命名
Type 返回数据类型
Redis一些命令
ps -aux | grep redis
/ append only 回车
shotdown
Reids位操作
redis位操作也叫做位数操作
bitmap提供四个用于操作二进制位数组
SETBIT
语法:fset value 命令key偏移量0/1
setbit命令用于写入位数组指定偏移量的二进制位设置值,偏移量从0开始计数,
且只允许写入1或者0,如果写入非0和1的值则写入失败:
GETBIT
语法:GETBIT key offset GETBIT key offset
getbit命令用于获取位数组指定偏移量上的二进制值:
BITCOUNT
语法:BITCOUNT key 命令 key
bitcount命令用于获取指定key的位数组中值为1的二进制位的数量,之前我们写入了偏移量0的值为1,偏移量10 的值为1,偏移量8的值为0:
BITTOP
语法:BITOP operation destkey key [key…] 命令 操作 结果目标key key1 key2 …
bitop命令可以对多个位数组的key进行and(按位与)、or(按位或)、xor(按位异或)运算,并将运算结果设置到destkey中:
用户签到场景
每天的日期字符串作为一个key,用户Id作为offset,统计每天用户的签到情况,总的用户签到数
活跃用户数统计
用户日活、月活、留存率等均可以用redis位数组来存储,还是以每天的日期作为key,用户活跃了就写入offset为用户id的位值1。
底层数据结构分析
SDS是redis中的一种数据结构,叫做简单动态字符串(Simple Dynamic String),并且它是一种二进制安全的,在大多数的情况下redis中的字符串都用SDS来存储。
struct sdshdr {
#记录buff数组中已使用字节的数量
#也是SDS所保存字符串的长度
int len;
#记录buff数组中未使用字节的数量
int free;
#字节数组,字符串就存储在这个数组里
char buff\[\];
}
SDS的优点:
时间复杂度为O(1)
杜绝缓冲区溢出
减少修改字符串长度时候所需的内存重分配次数
二进制安全的API操作
兼容部分C字符串函数
线上排查问题
top -c
top
top -H -p 12798 查看比较耗资源的线程
jstack 查看堆内存
jstack 12798 |grep 12799的16进制 31ff
dump了问题进程所有堆内存
debug tomcat
Arthas 是Alibaba开源的Java诊断工具
thread
thread -b, 找出当前阻塞其他线程的线程
常见问题1:CPU利用率高
频繁 FullGC/YongGC
查看 gc 日志
jstack -gcutil pid 查看内存使用和 gc 情况
代码消耗,如死循环,md5 等内存态操作
arthas (已开源:https://github.com/alibaba/arthas)
thread -n 5 查看 CPU 使用率最高的前 5 个线程(包含堆栈,第二部分有详解)
jstack 查找
ps -ef | grep java 找到 Java 进程 id
top -Hp pid 找到使用 CPU 最高的线程
printf ‘0x%x’ tid 线程 id 转化 16 进制
jstack pid | grep tid 找到线程堆栈
输入“1”可查看每个 CPU 的情况,之前有团队遇到单个 CPU 被中间件绑定导致 CPU 飚高的 case。
常见问题 2:load 高
load 指单位时间内活跃进程数,包含运行态(runnable 和 running)和不可中断态( IO、内核态锁)。
Sql优化 select where and and group by app_account 500万数量级的情况下,单表查询速度在30多秒
思路0 给app_account字段加索引。
思路1 后面应该加上 order by null;避免无用排序,但其实对结果耗时影响不大,还是很慢。
思路2 a)where条件太复杂,没索引,导致查询慢,调整where条件里字段的查询顺序,有索引的放前面。
b)给where条件的所有字段加上了组合索引,也还是没用
思路3 既然group by慢,换distinct试试
思路4 用子查询的方式,先查where条件里的内容,再去重。
最终解决方案
explain执行计划里(表索引建的太多了,一些索引并没有用到)
强制指定使用idx_end_time索引
所谓的sqlyog查询快,命令行查询慢的现象,已经找到原因了。
是因为sqlyog会在查询语句后默认加上limit 1000,所以导致很快。这个问题不再纠结。
Redis4.0
we started to make Redis more threaded.
Redis6.0
2020年5月2日正式发布
Redis 的瓶颈并不在 CPU,而在内存和网络。
网络协议RESP3
Threaded IO 指网络IO处理(默认不开启,需要配置文件种开启)
集群代理
ACL
多线程
1.Redis6.0之前版本是单线程吗?
Redis在处理客户端的请求时,包括获取(socket读)、解析、执行、内容返回(socket写)等都由一个顺序串行的主线程处理,这就是所谓的单线程
但如果严格来讲Redis4.0之后并不是单线程,除了主线程外,他也有后台线程在处理较为缓慢的操作,例如清理
2.Redis6.0之前为什么一直不适用多线程
官方曾做过类似问题的回复:使用Redis时,几乎不存在CPU成为瓶颈的情况,Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。
使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统的复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没必要使用多线程。单线程机制使得Redis内部实现的复杂
分布式锁解决方案
多线程
秒杀
特点
时间短,并发访量大
解决方案
1.秒杀系统独立部署,独立域名,与其他网站隔离
2.页面静态化,不需要因为用户频繁刷新而一直访问应用服务
3.秒杀突然新增远超平时的网络带宽,需提前与运营商购买或者租借,秒杀商品页面缓存在CDN,服务商临时租借新增的出口带宽。
4.避免直接下单,在秒杀前由服务端随机生成随机数作为参数拼接在普通URL上,
5.点亮按钮
该页面设计为静态页面,缓冲在CDN、反向代理服务器上,甚至是用户浏览器上,在秒杀开始钱,请求不会到达应用服务器
使用JavaSsript脚本控制,在秒杀商品静态页面中加入一个JavaScript文件,引用该JavaScript文件中包含秒杀开始标志为否;当秒杀开始的时候生成一个新的JavaScript文件(文件名保持不变,只是内容不一样)
即所有人看到的URL都是同一个,服务器端可以用redis这种分布式缓存服务器来保存随机数)
并被用户浏览器加载,控制秒杀商品页面的展示。这个JavaScript文件的加载可以加上随机版本号(例如xx.js?v=32353823),这样就不会被浏览器、CDN和反向代理服务器缓存。
这个JavaScript文件非常小,即使每次浏览器刷新都访问JavaScript文件服务器也不会对服务器集群和网络带宽造成太大压力。
流量
秒杀前(静态页面拦截流量)
如何解决秒杀前用户大量刷新页面?
静态页面(除了秒杀按钮需要的服务端判断),缓存到浏览器和CDN
redis读写分离(支持60万以上的qps)拦截流量
商品数量、是否开始秒杀(开始标记)、已经接收到的请求(余量=good_count - good_access)
主从版Redis提供10万级别的QPS
信息验证、确认订单
hash结构表
扣量时,服务器通过请求Redis获取下单资格,lua脚本(redis点线程,保证lua的原子性)
先使用SCRIPT LOAD将lua脚本提前缓存在Redis,然后调用EVALSHA调用脚本,比直接调用EVAL节省网络带宽:
使用redis实现简单消息队列(list)
实现异步下单入库(秒杀商品数据多时)
msyql
发生下单检验失败和退单等情况
需要定时将数据库中的数据进行一定的计算,同步到主从版Redis,同时再同步到读写分离的Redis,让更多的流量进来。
1、QPS
QPS Queries Per Second 是每秒查询率 ,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准, 即每秒的响应请求数,也即是最大吞吐能力。
2、TPS
TPS Transactions Per Second 也就是事务数/秒。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数,
3、QPS和TPS区别
个人理解如下:
1、Tps即每秒处理事务数,包括了
用户请求服务器
服务器自己的内部处理
服务器返回给用户
这三个过程,每秒能够完成N个这三个过程,Tps也就是N;
2、Qps基本类似于Tps,但是不同的是,对于一个页面的一次访问,形成一个Tps;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“Qps”之中。
例子:
例如:访问一个页面会请求服务器3次,一次放,产生一个“T”,产生3个“Q”
例如:一个大胃王一秒能吃10个包子,一个女孩子0.1秒能吃1个包子,那么他们是不是一样的呢?答案是否定的,因为这个女孩子不可能在一秒钟吃下10个包子,她可能要吃很久。这个时候这个大胃王就相当于TPS,而这个女孩子则是QPS。虽然很相似,但其实是不同的。
4、并发数
并发数(并发度):指系统同时能处理的请求数量,同样反应了系统的负载能力。这个数值可以分析机器1s内的访问日志数量来得到
5、吐吞量
吞吐量是指系统在单位时间内处理请求的数量,TPS、QPS都是吞吐量的常用量化指标。
系统吞吐量要素
一个系统的吞吐量(承压能力)与request(请求)对cpu的消耗,外部接口,IO等等紧密关联。
单个request 对cpu消耗越高,外部系统接口,IO影响速度越慢,系统吞吐能力越低,反之越高。
重要参数
QPS(TPS),并发数,响应时间
QPS(TPS):每秒钟request/事务 数量
并发数:系统同时处理的request/事务数
响应时间:一般取平均响应时间
关系
QPS(TPS)=并发数/平均响应时间
一个系统吞吐量通常有QPS(TPS),并发数两个因素决定,每套系统这个两个值都有一个相对极限值,在应用场景访问压力下,只要某一项达到系统最高值,系统吞吐量就上不去了,如果压力继续增大,系统的吞吐量反而会下降,原因是系统超负荷工作,上下文切换,内存等等其他消耗导致系统性能下降。
6、PV
PV(Page View):页面访问量,即页面浏览量或点击量,用户每次刷新即被计算一次。可以统计服务一天的访问日志得到。
7、UV
UV(Unique Visitor):独立访客,统计1天内访问某站点的用户数。可以统计服务一天的访问日志并根据用户的唯一标识去重得到。响应时间(RT):响应时间是指系统对请求作出响应的时间,一般取平均响应时间。可以通过Nginx、Apache之类的Web Server得到。
8、DAU
DAU(Daily Active User),日活跃用户数量。常用于反映网站、互联网应用或网络游戏的运营情况。DAU通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似
9、MAU
MAU(Month Active User):月活跃用户数量,指网站、app等去重后的月活跃用户数量
10、系统吞吐量评估
我们在做系统设计的时候就需要考虑CPU运算,IO,外部系统响应因素造成的影响以及对系统性能的初步预估。
而通常情况下,我们面对需求,我们评估出来的出来QPS,并发数之外,还有另外一个维度:日pv。
通过观察系统的访问日志发现,在用户量很大的情况下,各个时间周期内的同一时间段的访问流量几乎一样。比如工作日的每天早上。只要能拿到日流量图和QPS我们就可以推算日流量。
通常的技术方法:
1、找出系统的最高TPS和日PV,这两个要素有相对比较稳定的关系(除了放假、季节性因素影响之外)
2、通过压力测试或者经验预估,得出最高TPS,然后跟进1的关系,计算出系统最高的日吞吐量。B2B中文和淘宝面对的客户群不一样,这两个客户群的网络行为不应用,他们之间的TPS和PV关系比例也不一样。
11、软件性能测试的基本概念和计算公式
软件做性能测试时需要关注哪些性能呢?
首先,开发软件的目的是为了让用户使用,我们先站在用户的角度分析一下,用户需要关注哪些性能。
对于用户来说,当点击一个按钮、链接或发出一条指令开始,到系统把结果已用户感知的形式展现出来为止,这个过程所消耗的时间是用户对这个软件性能的直观印 象。也就是我们所说的响应时间,当相应时间较小时,用户体验是很好的,当然用户体验的响应时间包括个人主观因素和客观响应时间,在设计软件时,我们就需要 考虑到如何更好地结合这两部分达到用户最佳的体验。如:用户在大数据量查询时,我们可以将先提取出来的数据展示给用户,在用户看的过程中继续进行数据检 索,这时用户并不知道我们后台在做什么。
用户关注的是用户操作的相应时间。
其次,我们站在管理员的角度考虑需要关注的性能点。
1、 响应时间
2、 服务器资源使用情况是否合理
3、 应用服务器和数据库资源使用是否合理
4、 系统能否实现扩展
5、 系统最多支持多少用户访问、系统最大业务处理量是多少
6、 系统性能可能存在的瓶颈在哪里
7、 更换那些设备可以提高性能
8、 系统能否支持7×24小时的业务访问
再次,站在开发(设计)人员角度去考虑。
1、 架构设计是否合理
2、 数据库设计是否合理
3、 代码是否存在性能方面的问题
4、 系统中是否有不合理的内存使用方式
5、 系统中是否存在不合理的线程同步方式
6、 系统中是否存在不合理的资源竞争