点击“码农Academy”,关注,置顶大众,号
每日技巧干货,第一时光送达!
引言
在当今互联网范畴,尤其在大型电商平台如淘宝这样的庞杂散布式体系中,数据的高效管理和迅速拜访至关主要。面对数以千万计的商品、交易记载以及其他各类业务数据,如何在MySQL等传统关系型数据库之外,借助内存数据库Redis的力气,对部分高频拜访数据进行高效的缓存处置,是晋升全部体系性能的症结一环。
比如淘宝,京东,拼多多等电商体系每日处置的订单量级宏大,其数据库中存储的商品、用户信息及相干交易数据可达数千万条。为了下降数据库查询的压力,加速数据读取,Redis常被用于搭建二级缓存体系,以容纳部分最为活泼的“热点数据”。然而,在资源有限的情形下,如何确保仅有的20万条缓存数据精准匹配到体系中的热点数据,避免频繁的冷数据调换热数据导致的缓存失效,这就涉及到了一套精密的数据管理策略和缓存淘汰机制的设计。
本文将环绕这一实战场景展开讨论:在MySQL拥有2000万条数据的前提下,如何确保Redis仅缓存的20万条数据全都是体系中的热点数据,从而最大水平上施展缓存的优势,提高体系的响应速度和并发才能,进而晋升用户的购物体验和服务质量。通过对Redis内部机制的深刻懂得以及对业务场景的精致剖析,我们将揭示一套综合应用各种技巧手腕来确保Redis中热点数据精确有效的管理计划。
技巧背景
在探讨如何确保Redis中存储的20万数据均为热点数据之前,首先须要明白MySQL与Redis在实际业务环境中的互补关系以及Redis自身的内存管理和数据淘汰机制。
MySQL与Redis的关系及应用处景
MySQL作为一种成熟的关系型数据库管理体系,实用于存储大批持久化且具有庞杂关系的数据,其壮大的事务处置才能和安全性保障了数据的一致性和完全性。但在大范围并发环境下,尤其是对那些读多写少、拜访频次极高的热点数据,直接从MySQL中读取可能会成为体系性能瓶颈。
Redis则是一种高性能的内存键值数据库,以其极快的速度和灵巧的数据构造著称。在淘宝这类大型电商平台中,Redis主要用于缓存频繁拜访的数据,例如热点商品信息、用户购物车、会话状况等,以此减轻主数据库的压力,提高响应速度,加强体系的可扩大性和容错性。
对于Redis高性能原理,请参考:京东二面:Redis为什么快?我说Redis是纯内存操作的,然后他对我笑了笑。
对于Redis的应用的业务场景,请参考:美团一面:项目中应用过Redis吗?我说用Redis做缓存。他对我哦了一声
Redis内存管理和数据淘汰机制简介
Redis的所有数据都存储在内存中,这意味着它的容量相较于磁盘存储更为有限。为懂得决内存容量不足的问题,Redis供给了多种数据淘汰策略。其中,与保证热点数据亲密相干的是LFU(Least Frequently Used)策略,它能够依据数据对象的拜访频次,将拜访次数最少(即最不常用)的数据淘汰出内存,以便为新的数据腾出空间。
对于Redis高性能的一方面原因就是Redis高效的管理内存,具体请参考:京东二面:Redis为什么快?我说Redis是纯内存操作的,然后他对我笑了笑。
此外,Redis许可用户依据自身需求选择不同的淘汰策略,例如“volatile-lfu”只针对设置了过期时光的key采取LFU算法,“allkeys-lfu”则对所有key都履行LFU淘汰规矩。
热点数据定义及其辨认办法
热点数据是指在必定时光内拜访频率极高、对体系性能影响重大的数据集。在电商平台中,这可能表示为热销商品详情、运动页面信息、用户高频查询的搜索症结词等。辨认热点数据主要依附于对业务日志、要求统计和体系性能监控工具的剖析,通过收集和剖析用户行动数据,发现并量化哪些数据是体系拜访的热点,以便有针对性地将它们缓存至Redis中。
实现计划
在实际应用中,确保Redis中存储的数据为热点数据,我们可以从以下几个计划斟酌实现。
LFU淘汰策略
Redis中的LFU(Least Frequently Used)淘汰策略是一种基于拜访频率的内存管理机制。当Redis实例的内存应用量到达预先设定的最大内存限制(由maxmemory
配置项指定)时,LFU策略会依据数据对象的拜访频次,将拜访次数最少(即最不常用)的数据淘汰出内存,以便为新的数据腾出空间。
LFU算法的核心思想是通过跟踪每个键的拜访频率来决议哪些键应该优先被淘汰。具体实现上,Redis并非实时精确地盘算每个键的拜访频率,而是采取了近似的LFU办法,它为每个键保护了一个拜访计数器(counter)。每当某个键被拜访时,它的计数器就会递增。随着时光推移,Redis会依据这些计数器的值来决议淘汰哪些键。
在Redis 4.0及其后续版本中,LFU策略可以通过设置maxmemory-policy
配置项为allkeys-lfu
或volatile-lfu
来启用。其中:
•
allkeys-lfu
:实用于所有键,无论它们是否有过期时光,都会基于拜访频率淘汰键。•
volatile-lfu
:仅针对设置了过期时光(TTL)的键,依照拜访频率淘汰键。
Redis实现了自己的LFU算法变体,它应用了一个基于拜访计数和老化时光的组合策略来更好地适应实际情形。这意味着不仅斟酌拜访次数,还会斟酌到键的拜访频率随时光的变更,防止长期未拜访但曾经很热点的键占领大批内存空间而不被淘汰。在实现上,Redis应用了一种称为“频率跳表(frequency sketch)”的数据构造来存储键的拜访频率,许可迅速查找和更新计数器。为了避免长期未拜访但计数器较高的键永久保存,Redis会在一段时光后下降键的拜访计数,模仿拜访频率随时光衰减的后果。
在Redis中应用LFU淘汰策略,在配置文件redis.conf
中找到maxmemory-policy
选项,将其设置为LFU相干策略之一:
maxmemory-policy allkeys-lfu # 对所有键启用LFU淘汰策略
# 或者
maxmemory-policy volatile-lfu # 对有过期时光的键启用LFU淘汰策略
确保你也设置了Redis的最大内存应用量(maxmemory
),只有当内存到达这个上限时,才会触发淘汰策略:
maxmemory <size_in_bytes> # 指定Redis可以应用的最大内存大小
LFU策略旨在尽可能让那些近期最不活泼的数据优先被淘汰,以此坚持缓存中的数据相对活泼度更高,提高缓存命中率,从而晋升体系的整体性能。(这也是我们面试中须要答复出来的答案)
LRU淘汰策略
Redis中的LRU(Least Recently Used)淘汰策略是一种用于在内存不足时自动删除最近最少应用的数据以回收内存空间的办法。尽管Redis没有完全精确地实现LRU算法(因为这在O(1)时光内实现成本较高),但Redis确切供给了一种近似LRU的行动。
当我们配置了最大内存限制,如果内存超越这个限制时,Redis会选择性地删除一些键值对来腾出空间。Redis供给了几种不同的淘汰策略,其中之一就是volatile-lru
和allkeys-lru
,这两种都试图模仿LRU行动。
• volatile-lru:仅针对设置了过期时光(TTL)的键,依照最近最少应用的原则来删除键。
• allkeys-lru:不论键是否设置过期时光,都会依据最近最少应用的原则来删除键。
Redis实现LRU的方法并不是真正意义上的双向链表加引用计数这样的完全LRU构造,因为每个键值对的插入、删除和拜访都须要保持这样的数据构造会带来额外的开销。所以Redis实现LRU会采取以下方法进行:
1. Redis内部为每个键值对保护了一个“空转时光”(idle time)的字段,它是在Redis实例启动后最后一次被拜访或修正的时光戳。
2. 当内存到达阈值并触发淘汰时,Redis不会遍历全部键空间找出绝对意义上的最近最少应用的键,而是随机抽取一批键检讨它们的空转时光,然后删除这批键中最久未被拜访的那个。Redis在大多数情形下能较好地模仿LRU后果,有助于坚持活泼数据在内存中,减少因频繁换入换出带来的性能丧失。
内存淘汰策略通常是在Redis服务器端的配置文件(如redis.conf
)中设置,而不是在应用中配置。你须要在Redis服务器端的配置中设置maxmemory-policy
参数为allkeys-lru
。(同LFU策略)
应用Redis的LRU淘汰策略实现热点数据的方法,简略易行,能较好地应对大部分情形下的热点数据问题。但是若拜访模式庞杂或数据拜访散布不均匀,单纯的LRU策略可能不够精准,不能确保绝对的热点数据留存。
联合拜访频率设定过期时光
在实际应用中,除了依附Redis的淘汰策略外,还可以联合业务逻辑,依据数据的拜访频率动态设置Key的过期时光。例如,当某个Key被频繁拜访时,延伸其在Redis中的有效期,反之则缩短。
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void updateKeyTTL(String key, int ttlInSeconds) {
redisTemplate.expire(key, ttlInSeconds, TimeUnit.SECONDS);
}
// 示例调用,当检测到某个数据拜访增多时,增长其缓存过期时光
public void markAsHotSpot(String key) {
updateKeyTTL(key, 3600); // 将热点数据缓存时光延伸至1小时
}
这种方法灵巧性强,可依据实际拜访情形动态调剂缓存策略。但是须要在应用程序中进行较多定制开发,以捕捉并响应数据拜访的变更。
基于时光窗口的缓存淘汰策略
在给定的时光窗口(如过去1小时、一天等)内,对每个数据项的拜访情形进行实时跟踪和记载,可以应用计数器或其他数据构造统计每条数据的拜访次数。到达时光窗口边界时,盘算每个数据项在该窗口内的拜访频率,这可以是绝对拜访次数、相对拜访速率或者其他反应拜访热度的指标。依据预先设定的阈值,将拜访次数超过阈值的数据项参加Redis缓存,或者将其缓存时光延伸以确保其能在缓存中停留更久。而对于拜访次数低于阈值的数据项,要么从缓存中移除,要么缩短其缓存有效期,使其更容易被后续淘汰策略处置。
@Service
public class TimeWindowCacheEvictionService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final Map<String, AtomicInteger> accessCounts = new ConcurrentHashMap<>();
// 时光窗口长度(例如,1小时)
private static final long TIME_WINDOW_MILLIS = TimeUnit.HOURS.toMillis(1);
@Scheduled(fixedRate = TIME_WINDOW_MILLIS)
public void evictBasedOnFrequency() {
accessCounts.entrySet().forEach(entry -> {
int accessCount = entry.getValue().get();
if (accessCount > THRESHOLD) { // 假设THRESHOLD是拜访次数阈值
// 将数据存入或更新到Redis缓存,并设置较长的过期时光
redisTemplate.opsForValue().set(entry.getKey(), getDataFromDB(entry.getKey()), CACHE_EXPIRATION_TIME, TimeUnit.MINUTES);
} else if (redisTemplate.hasKey(entry.getKey())) {
// 拜访次数低,从缓存中移除或缩短过期时光
redisTemplate.delete(entry.getKey());
}
});
// 清零拜访计数器,预备下一个时光窗口
accessCounts.clear();
}
public void trackDataAccess(String dataId) {
accessCounts.computeIfAbsent(dataId, k -> new AtomicInteger()).incrementAndGet();
}
}
关于@Scheduled是Springboot中实现定时义务的一种方法,对于其他几种方法,请参考:玩转SpringBoot:SpringBoot的几种定时义务实现方法
通过这种办法,体系能够基于实际拜访情形动态调剂缓存内容,确保Redis缓存中寄存的总是具有必定热度的数据。当然,这种办法须要与实际业务场景紧密联合,并联合其他缓存策略共同作用,以实现最优后果。同时,须要注意此种策略可能带来的额外盘算和存储成本。
手动缓存掌握
针对已辨认的热点数据,可以通过监听数据库变革或业务逻辑触发器自动将数据更新到Redis中。例如,当商品销量剧增变为热点商品时,立即更新Redis缓存。
这种方法可以确保热点数据及时更新,提高了缓存命中率。
应用数据构造优化
应用Sorted Set等数据构造可以进一步精致化热点数据管理。例如,记载每个商品最近的拜访的活泼时光,并据此决议缓存哪些商品数据。
// 商品拜访活泼时更新其在Redis中的排序
String goodsActivityKey = "goods_activity";
redisTemplate.opsForZSet().add(goodsActivityKey, sku, System.currentTimeMillis());
// 定时消除较早的非热点商品数据
@Scheduled(cron = "0 0 3 * * ?") // 每天清晨3点清算前一天的数据
public void cleanInactiveUsers() {
long yesterdayTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
redisTemplate.opsForZSet().removeRangeByScore(goodsActivityKey, 0, yesterdayTimestamp);
}
这种方法能够充足应用Redis内建的数据构造优势,实现庞杂的数据淘汰逻辑。
实际业务中实践计划
在例如淘宝这样宏大的电商生态体系中,面对MySQL中海量的业务数据和Redis有限的内存空间,我们采取了多元化的策略以确保缓存的20万数据是真正的热点数据。
LFU策略的应用
自Redis 4.0起,我们可以通过配置Redis淘汰策略为近似的LFU(volatile-lfu
或 allkeys-lfu
),使得Redis能够自动依据数据拜访频率进行淘汰决策。LFU策略基于数据的拜访次数,使得拜访越频繁的数据越不容易被淘汰,从而更好地坚持了热点数据在缓存中的存在。
拜访频率动态调剂
除了依附Redis内置的LFU淘汰策略,我们还可以实现应用层面的拜访频率追踪和响应式缓存管理。例如,每当商品被用户拜访时,体系会更新该商品在Redis中的拜访次数,同时依据拜访频率动态调剂缓存过期时光,确保拜访频率高的商品在缓存中的生存期得到延伸。
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Product> redisTemplate;
public void updateProductViewCount(String productId) {
// 更新产品拜访次数
redisTemplate.opsForValue().increment("product:view_count:" + productId);
// 依据拜访次数调剂缓存过期时光
Long viewCount = redisTemplate.opsForValue().get("product:view_count:" + productId);
if (viewCount > THRESHOLD_VIEW_COUNT) {
redisTemplate.expire("product:info:" + productId, LONGER_CACHE_EXPIRATION, TimeUnit.MINUTES);
}
}
}
数据构造优化
我们还可以应用Redis丰硕的数据构造,如有序聚集(Sorted Sets)和哈希(Hashes),来实现商品热度排行、用户行动剖析等功效。例如,通过Sorted Set存储商品的阅读量,自动依照阅读量高下进行排序,并淘汰拜访量低的商品缓存。
// 更新商品阅读量并同步到Redis有序聚集
public void updateProductRanking(String productId, long newViewCount) {
redisTemplate.opsForZSet().add("product_ranking", productId, newViewCount);
// 自动淘汰阅读量低的商品缓存
redisTemplate.opsForZSet().removeRange("product_ranking", 0, -TOP_RANKED_PRODUCT_COUNT - 1);
}
总结
本文详细论述了在电商平台例如淘宝及其他相似场景下,如何联合LFU策略与拜访频率调剂,优化Redis中20万热点数据的管理。通过配置Redis近似的LFU淘汰策略,联合应用层面对拜访频率的实时追踪与响应式调剂,以及应用多样化的Redis数据构造如有序聚集和哈希表,胜利实现了热点数据的精确缓存与淘汰。
通过电商平台的一些实际业务实践证明,这种综合策略可以有效晋升缓存命中率,下降数据库拜访压力,确保缓存资源始终服务于拜访最频繁的数据。未来随着数据发掘与剖析技巧的提高,以及Redis或其他内存数据库功效的拓展,预计将进一步细化和完美热点数据的辨认与管理机制。例如,摸索更具前瞻性的预测性缓存策略,或是联合机器学习模型对用户行动进行深度剖析,以更精准地预判和存储未来的热点数据。
版权声明:本文系大众,号 "码农Academy" 原创,转载、引用本文内容请注明出处
关注大众,号后回复[1024],无套路免费获取一份架构师学习资料
关注大众,号后回复[面试],无套路免费获取一份大厂面试资料