** 读缓存**
-
分布式缓存
先将所有的缓存数据集中存储在同一个地方,而非重复保存到各个服务器节点中,然后所有的服务器节点都从这个地方读取数据
使用MongoDB的公司最少,目前,Redis比Memcached更流行:
(1)数据结构:在使用Memcached保存List缓存对象的过程中,如果往List中增加一条数据,则首先需要读取整个List,再反序列化塞入数据,接着再序列化存储回Memcached。而对于Redis而言,这仅仅是一个Redis请求,它会直接帮助塞入数据并存储,简单快捷
(2)持久化:对于Memcached来说,一旦系统宕机数据就会丢失。因为Memcached的设计初衷就是一个纯内存缓存
(3)集群:Memcached的集群设计非常简单,客户端根据Hash值直接判断存取的Memcached节点。而Redis的集群因在高可用、主从、冗余、Failover等方面都有所考虑,所以集群设计相对复杂些,属于较常规的分布式高可用架构 -
缓存何时存储数据
-
使用缓存的逻辑如下。
-
1)先尝试从缓存中读取数据。
-
2)若缓存中没有数据或者数据过期,再从数据库中读取数据保存到缓存中。
-
3)最终把缓存数据返回给调用方
-
4)注意,存在数据库崩溃的情况,当大量缓存不存在的时候导致。
-
数据库的崩溃可以分为3种情况
1)单一数据过期或者不存在,这种情况称为缓存击穿。
解决方案:第一个线程如果发现Key不存在,就先给Key加锁,再从数据库读取数据保存到缓存中,最后释放锁。
如果其他线程正在读取同一个Key值,那么必须等到锁释放后才行。
2)数据大面积过期或者Redis宕机,这种情况称为缓存雪崩。
解决方案:设置缓存的过期时间为随机分布或设置永不过期即可。
3)一个恶意请求获取的Key不在数据库中,这种情况称为缓存穿透。
比如正常的商品ID是从100000到1000000(10万到100万之间的数值),那么恶意请求就可能会故意请求2000000以上的数据。这种情况如果不做处理,恶意请求每次进来时,肯定会发现缓存中没有值,那么每次都会查询数据库,虽然最终也没在数据库中找到商品,但是无疑给数据库增加了负担。
这里给出两种解决办法。
①在业务逻辑中直接校验,在数据库不被访问的前提下过滤掉不存在的Key。
②针对恶意请求的Key存放一个空值在缓存中,防止恶意请求骚扰数据库。
4)缓存预热:用户请求过来之前把数据都缓存到Redis中 -
如何更新缓存: 更新数据库和更新缓存
先删除缓存,更新数据库,再删除缓存 -
缓存的高可用设计
1)负载均衡:是否可以通过加节点的方式来水平分担读请求压力。
2)分片:是否可以通过划分到不同节点的方式来水平分担写压力。
3)数据冗余:一个节点的数据如果失效,其他节点的数据是否可以直接承担失效节点的职责。
4)Failover:任何节点失效后,集群的职责是否可以重新分配以保障集群正常工作。
5)一致性保证:在数据冗余、Failover、分片机制的数据转移过程中,如果某个地方出了问题,
能否保证所有的节点数据或节点与数据库之间数据的一致性(依靠Redis本身是不行的)
6)如果对缓存高可用有需求,可以使用Redis的Cluster模式 -
缓存的监控
缓存上线以后,还需要定时查看其使用情况,再判断业务逻辑是否需要优化
在查看缓存使用情况时,一般会监控缓存命中率、内存利用率、慢日志、延迟、客户端连接数等数据
写缓存
写缓存的思路是后台服务接收到用户请求时,如果请求校验没问题,数据并不会直接落库,而是先存储在缓存层中,缓存层中写请求达到一定数量时再进行批量落库。
这里所说的缓存层实际上指的就是写缓存。它的意义在于利用写缓存比数据库高几个量级的吞吐能力来承受洪峰流量,再匀速迁移数据到数据库
数据库如果用批量插入语句,TPS也是可以非常高的,可能达到上万,这样不仅能防止数据库崩溃,还能确保用户的请求得到满足
在具体实施过程中要考虑6个问题。
1)写请求与批量落库这两个操作同步还是异步?
根据业务实际考虑
2)如何触发批量落库?
批量落库触发逻辑:
1)写请求满足特定次数后就落库一次,比如10个请求落库一次。
优点:是访问数据库的次数变为1/N,从数据库压力上来说会小很多。
不足:如果访问数据库的次数未凑齐N次,用户的预约就一直无法落库。
2)每隔一个时间窗口落库一次,比如每隔一秒落库一次。
优点:能保证用户等待的时间不会太久,
缺点:如果某个瞬间流量太大,在那个时间窗口落库的数据就会很多,多到在一次数据库访问中无法完成所有数据的插入操作(比如一秒内堆积了5000条数据),它们只能通过分批次来实现插入
3)两种方式一起使用
1)每收集一次写请求,就插入预约数据到缓存中,再判断缓存中预约的总数是否达到一定数量,达到后直接触发批量落库。
2)开一个定时器,每隔一秒触发一次批量落库
3)缓冲数据存储在哪里?
存放在本地内存中,
存放在分布式缓存中(比如Redis),
其中最简单的方式是存放在本地内存中
4)缓存层并发操作需要注意什么?
缓存层并发操作逻辑与冷热分离迁移冷数据的逻辑很相似
批量落库的实现逻辑:
1)当前线程从缓存中获取所有数据。
2)当前线程批量保存数据到数据库。
3)当前线程从缓存中删除对应数据(注意:不能直接清空缓存的数据,因为新的预约数据可能插入到缓存中了)
5)批量落库失败了怎么办?
6)Redis的高可用配置
Redis的数据备份
Redis主从功能
统一管理的Redis集群方案:
1)先使用简单的主从模式。
2)然后在Slave Redis里使用快照(30秒一次)+AOF(一秒一次)的配置。
3)如果Master Redis宕机了,千万不要直接启动,先把Slave Redis升级为Master Redis。
4)这时代码里已经有预案了,写缓存如果失败直接落库。
不过这个方案有个缺点,即一旦系统宕机,手动恢复时大家就会手忙脚乱,但数据很有保障。
-
Kafka是LinkedIn推出的开源消息中间件,它天生是为收集日志而设计的,而且具备超高的吞吐量和数据量扩展性,被称作无限堆积
-
Kafka的存储结构中每个Topic分区相当于一个巨型文件,而每个巨型文件又是由多个Segment小文件组成的。
其中,Producer负责对该巨型文件进行“顺序写”,Consumer负责对该文件进行“顺序读”。
可以把Kafka的存储架构简单理解为,Kafka写数据时通过追加数据到文件末尾来实现顺序写,读取数据时直接从文件中读,这样做的好处是读操作不会阻塞写操作,这也是其吞吐量大的原因。
理论上只要磁盘空间足够,Kafka就可以实现消息无限堆积,因此它特别适合处理日志收集这种场景
-
目前流行的实时处理工具主要为Storm、Spark Streaming、Apache Flink
Apache Flink,不仅因为它性能强(阿里采用这项技术后,活动期间一秒内能够处理17亿条数据),
还因为它的容错机制能保证每条数据仅仅处理一次,而且它有时间窗口处理功能。
在实际业务场景中,如果需要按照时间窗口统计数据,往往是根据消息的事件时间来计算。
Apache Flink的特性恰恰是使用了基于消息的事件时间,而不是基于计算框架的处理时间,这也是它的另一个撒手锏 -
不同流处理框架中采取不同的容错机制,能够保证不一样的一致性。
-
1)At-Most-Once:至多一次,表示一条消息不管后续处理成功与否只会被消费处理一次,存在数据丢失的可能。
-
2)Exactly-Once:精确一次,表示一条消息从其消费到后续的处理成功只会发生一次。
-
3)At-Least-Once:至少一次,表示一条消息从消费到后续的处理成功可能会发生多次,存在重复消费的可能。
-
架构的流程:
-
1)后台服务端会记录所有的请求数据,存放到本地的日志文件。
-
2)使用数据收集框架Logstash,从日志文件抽取原始的日志数据,不加工直接存放到Kafka当中。
-
3)通过Apache Flink从Kafka中拉取原始的日志数据,并且经过业务加工,分别存放到Elasticsearch、HBase和MySQL中。
-
4)Elasticsearch用来处理用户针对请求日志的查询请求,它将查询关键字段的值和请求ID存放到索引中,跟进查询关键字获得结果ID的列表,再通过结果ID去HBase中获取详细的请求数据。
-
5)MySQL存放一些组合加工后的数据,用来做结算,结算的数据查询和处理请求量不大。
秒杀架构 -
秒杀架构的原则:
遵循商品不能超卖
下单成功的订单数据不能丢失
服务器和数据库不能崩溃
尽量别让机器人抢走商品 -
秒杀架构的设计方案就是一个不断过滤请求的过程。目标是尽量在上层处理用户请求,不让其往下层游动。
-
静态资源与负载均衡
CDN:不用花费自己的服务器资源和带宽,且响应速度快。静态资源的压力拦截在系统分层的外面
动态的请求3种实现方式。
1)通过JS(JavaScript)在后台动态调用。可以把动态的数据与页面进行整合,然后再放入CDN。也可以把它放在Redis缓存中,
2)JS访问服务器获取,针对获取服务器时间的这个请求,把它放在静态资源或负载均衡那层即可,这样用户请求就不会进入系统下游。
3)放在Cookie中。
总体来说,对于浏览页面的用户行为,需要把用户请求尽量拦截在CDN、静态资源或负载均衡侧,如果确实做不到,也要拦截在缓存中。 -
网关页面如何将请求拦截在上游
那在网关层面如何实现请求过滤呢?可以做3种限制。
1)限定每个用户的访问频率,比如每5秒下单一次。nginx上就能快速完成配
2)限定每个IP的访问频率。nginx上就能快速完成配。这种方式是为了避免有人通过机器人自动下单,导致错杀真实用户。
3)把一个时间段内的请求拦截掉一定比例,或者只允许特定数量的请求进入后台服务器。这里可以使用限流的漏桶或令牌桶算法 -
后台服务器过滤请求:目标已经不是如何过滤请求了,而是如何保证商品不超卖
1)商品数据放入缓存Redis中(在秒杀期间不做上架或修改库存之类的业务操作,即不通过技术,而是通过业务流程来保证)
2)订单写入缓存中
3)订单批量落库
4)Redis停止工作(挂掉)怎么办。
比如读Redis中的库存时,如果失败了,那就让它直接去数据库扣减库存,把那些incr和decr的逻辑放到数据库去;
若是把订单写入缓存的时候失败了,那就直接将订单数据写入数据库中,然后就不需要处理后面批量落库的逻辑了 -
付款页面如何将请求拦截在上游
这个环节,除了保障数据的一致性外,还有一个要点:
如果业务逻辑中出现了一个订单未及时付款而被取消的情况,记得把数据库及Redis的库存加回去 -
整体:静态资源服务器、网关、后台服务器均需要配置负载均衡,而缓存Redis和数据库均需要配置集群模式