Redis应用—1.在用户数据里的应用
大纲
1.社区电商的业务闭环
2.Redis缓存架构的典型生产问题
3.用户数据在读多写少场景下的缓存设计
4.热门用户数据的缓存自动延期机制
5.缓存惊群与穿透问题的解决方案
6.缓存和数据库双写不一致问题分析
7.基于分布式锁保证缓存和数据库双写一致性
8.缓存和数据库双写在分布式锁高并发下的优化
9.利用分布式锁自动超时消除串行等待锁的影响
10.写少读多的企业级缓存架构设计总结
1.社区电商的业务闭环
接下来介绍的社区电商是以Redis作为主体技术、以MySQL和RocketMQ作为辅助技术实现的。
(1)社区电商运作模式
社区电商的关键点在于社区,而电商则是辅助性质(次要地位,流量变现)。社区可以分成很多种社区,比如美食社区、美妆社区、影评社区、妈妈社区。社区平台也有很多很多,一般中小型的社区平台居多,比如体育社区、汽车社区、本地生活社区。
比如美食社区APP:用户可以分享积累的美食食谱、或者对美食看法、甚至是出门体验的一些餐馆,用户还可以浏览其他用户发出的一些美食食谱、体验、经历、科普。用户通过浏览其他用户发的帖子,对其进行互动、关注、私信、交流、成为好友。这样一部分用户就可以成立平台里的一个私密圈子,基于美食兴趣爱好进行社交活动。
美食社区APP里会有一些用户发出的帖子内容特别优质,这些帖子内容会吸引很多用户来浏览,浏览量可能会非常大。这时可以在这些帖子添加一些推荐商品,这样浏览帖子的用户就会看到推荐的商品,可以点击商品链接,进入商品详情页,发生购物行为。
(2)电商APP的feed流
feed流指的是APP不断地、主动地显示各种新内容给用户,如果用户主动搜索和浏览就不是feed流。
用户在电商APP首页不断进行下拉时:电商APP会根据用户的喜好、爆款,通过算法不停地显示一批新的内容给用户,这种展示商品的方式就是电商APP的feed流。
比如当用户进入社区电商APP的首页后,进行不停下拉时:电商APP会把用户关注过的大v、可能感兴趣的美食帖子、浏览量高的爆款帖子,通过算法不停地计算出新的一批内容显示给用户, 这就社区电商的feed流。
此外,用户还可以在社区电商APP根据条件和分页进行结构化查询。
(3)社区电商的流量变现交易闭环
对于小红书这些社区电商APP来说,当社区互动做好了之后,APP就会吸引大量流量。而对于这种有大量流量的社区APP,流量变现的最好模式就是种草。
所谓种草就是用户发布分享帖子时,可以在分享内容里插入一些商品推荐并给出商品链接。这样当其他用户在浏览这些分享帖子时,就会看到推荐的商品和链接。然后点击链接就可以进入商品详情页,查看商品标题、图文视频介绍、价格、营销、库存等。接着加入购物车并发起订单提交、支付、履约,最后就能拿到商品。
接下来主要介绍社区电商部分功能点的实现:首页feed流、帖子分享浏览详情、社交分享和团购、商品详情和库存、购物车,这些功能会基于Redis的企业级缓存方案(主要) + RocketMQ(部分)来实现。
2.Redis缓存架构的典型生产问题
Redis的典型生产问题如下:
问题一:热key问题
热key就是某个key形成了热点。比如某明星突然官宣离婚,那么就会出现大量用户瞬时涌入该明星微博进行围观的情况。从而出现瞬时百万级千万级请求去获取Redis某个key的数据,这就是热key问题。
对于社区电商APP来说,如果有一个比较好的帖子分享和团购活动,那么也有可能短时间内引发大量用户把这该帖子详情页分享到微信等社交应用。从而引发大量用户在短时间内查看该分享详情页,最后造成Redis热key问题。
问题二:大value问题
存储的key-value特别大,比如value多达10M。这个value如果被频繁读取,那么就有可能把Redis机器的网络带宽打满,阻塞别的请求。
问题三:缓存穿透击穿问题
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求(布隆过滤器或设置空对象)。缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发请求特别多,同时读缓存没读到数据,又同时去数据库去取数据。
问题四:缓存失效和LRU被清理的问题
缓存数据设置了过期的时间,到期失效后应该如何来处理。Redis如果内存满了,LRU算法会自动淘汰一些数据,对于这些数据应该如何进行处理,如何才能实现自动加载和重建。
问题五:缓存雪崩问题
缓存雪崩是指缓存中数据大批量到过期时间,而查询量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是:缓存击穿是指并发查同一条数据,缓存雪崩是不同数据都过期了。
如果Redis集群都崩掉了,只有数据库可以访问。那么首先就需要自动识别出缓存故障,然后马上进行限流对数据库进行保护,不让数据库崩溃,以及马上启动各个接口的降级机制。
各个接口的降级机制可以提前在JVM内存里,准备少量缓存作为降级备用数据。所以每个接口都需要有一个降级方案,一旦出现缓存故障,那么就可以自动限流避免数据库崩溃。
限流 -> 降级 -> 把JVM内存里缓存的默认数据给用户或者 直接对用户进行提醒。
问题六:数据库的一致性问题
缓存数据和数据库之间的一致性的保障,双写、异步同步如何保证一致性。
Redis生产总结:
Redis上了生产以后,首先需要模拟出足量的数据写入Redis里,比如模拟出千万级数据量写入部署好的Redis集群中。然后进行高并发压测,并通过CacheCloud进行监控运维。监控出有多少个缓存节点、里面放了多少G数据、大压力下接口性能如何、QPS多少、Redis机器负载如何、缓存命中率如何、数据库回源比例是多少、演示Redis节点故障的主从切换、演示Redis集群扩容等。
3.用户数据在读多写少场景下的缓存设计
具体的缓存设计如下:
一.新增或更新用户时先获取分布式锁,避免短时间发生多次请求出现重复新增或更新
二.用户数据会先写数据库,再写Redis缓存
三.用户数据属于读多写少场景下的数据,适合用缓存支持高并发场景下的读取
四.由于大部分用户数据属于冷门数据,故其缓存的过期时间设置为2天加随机几小时
五.如果后面有请求需要频繁访问某条用户数据,那么可以不断延长(重置)其缓存的过期时间
具体的代码如下:
@Override
public SaveOrUpdateUserDTO saveOrUpdateUser(SaveOrUpdateUserRequest request) {//新增或更新用户时先需要获取分布式锁,避免短时间发生多次请求出现重复新增或更新,保证幂等性String userUpdateLockKey = RedisKeyConstants.USER_UPDATE_LOCK_PREFIX + request.getOperator();boolean lock = redisLock.lock(userUpdateLockKey);if (!lock) {log.info("获取锁失败,operator:{}", request.getOperator());throw new BaseBizException("新增/修改失败");}try {//用户数据先写入数据库CookbookUserDO cookbookUserDO = cookbookUserConverter.convertCookbookUserDO(request);cookbookUserDO.setUpdateUser(request.getOperator());if (Objects.isNull(cookbookUserDO.getId())) {cookbookUserDO.setCreateUser(request.getOperator());}cookbookUserDAO.saveOrUpdate(cookbookUserDO);CookbookUserDTO cookbookUserDTO = cookbookUserConverter.convertCookbookUserDTO(cookbookUserDO);//用户数据写入数据库后,再写入Redis缓存,并生成随机的过期时间//每条用户数据都会在Redis里缓存2天加上随机几小时//这样后续出现高并发读取用户数据时,就可以直接从Redis缓存里获取用户数据了//而且用户数据一般是不会变化的,属于读多写少场景下的数据//像这种读多写少场景下的数据,就非常适合用缓存来支持高并发场景下的读取//此外,由于大部分用户数据不会被经常读取,所以可以设置默认来2天多就可以过期了//如果后面有请求需要访问这条用户数据,但在缓存里没找到,那么再从数据库里加载出写入缓存即可//如果后面有请求需要频繁访问这条用户数据,那么可以不断延长其缓存的过期时间redisCache.set(RedisKeyConstants.USER_INFO_PREFIX + cookbookUserDO.getId(),JsonUtil.object2Json(cookbookUserDTO), CacheSupport.generateCacheExpireSecond());SaveOrUpdateUserDTO dto = SaveOrUpdateUserDTO.builder().success(true).build();return dto;} finally {redisLock.unlock(userUpdateLockKey);}
}public interface CacheSupport {Integer TWO_DAYS_SECONDS = 2 * 24 * 60 * 60;//生成缓存过期时间:2天加上随机几小时static Integer generateCacheExpireSecond() {return TWO_DAYS_SECONDS + RandomUtil.genRandomInt(0, 10) * 60 * 60;}
}
4.热门用户数据的缓存自动延期机制
由于用户数据是属于读多写少场景下的数据,所以在新增或更新时对其进行缓存是很合适的。进行缓存后,在其他各种场景下,获取用户数据时就可以直接从缓存里进行读取。
为什么对用户数据进行缓存时,过期时间被设定为:2天加上随机几小时?
因为只有少数的用户数据是热门数据,这些用户发表的分享会占据绝大部分的浏览量。而大部分的用户数据都是冷门数据,这些用户发表的分享几乎没有多少浏览量。因此没有必要让所有的用户数据都驻留在缓存里,占用缓存宝贵的内存空间。
如果缓存好的某用户数据后续没有被访问,那么就让它过期即可。当过一段时间后有请求需要访问该用户数据时,再重新回源数据库进行查询并写入缓存。
如果缓存好的一条用户数据后续被不断请求访问,成为了热门用户数据。那么每次访问该用户数据,可以延长(重置)其缓存的过期时间。这样就可以从热门数据一直都可以从缓存里读取,实现高并发读。
注意:从缓存里获取不到用户数据,需要回源数据库进行查询并写入缓存时,首先要加分布式锁。
@Override
public CookbookUserDTO getUserInfo(CookbookUserQueryRequest request) {Long userId = request.getUserId();//先从缓存中尝试获取用户数据CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}//回源数据库进行读取return getUserInfoFromDB(userId);
}private CookbookUserDTO getUserFromCache(Long userId) {String userInfoKey = RedisKeyConstants.USER_INFO_PREFIX + userId;String userInfoJsonString = redisCache.get(userInfoKey);log.info("从缓存中获取用户信息,userId:{},value:{}", userId, userInfoJsonString);if (StringUtils.hasLength(userInfoJsonString)) {//通过设置空值来防止缓存穿透if (Objects.equals(CacheSupport.EMPTY_CACHE, userInfoJsonString)) {return new CookbookUserDTO();}//每次有读请求,就自动延长过期时间redisCache.expire(RedisKeyConstants.USER_INFO_PREFIX + userId, CacheSupport.generateCacheExpireSecond());CookbookUserDTO dto = JsonUtil.json2Object(userInfoJsonString, CookbookUserDTO.class);return dto;}return null;
}
5.缓存惊群与穿透问题的解决方案
(1)用户数据写入和查询时的缓存设计总结
(2)设置过期时间为2天加随机几小时的原因
(3)缓存穿透问题的解决方案
(1)用户数据写入和查询时的缓存设计总结
写入用户数据时,会对数据库和缓存进行双写,缓存默认2天多随机时间过期。如果用户数据被频繁读取,那么其缓存就会不停地自动延期(重置)。如果用户数据没被读取,那么就按默认设定自动过期,避免占用缓存空间。当用户数据不能从缓存中读取到,则先从数据库里查出来,然后再放入缓存里。
(2)设置过期时间为2天加随机几小时的原因
随机几小时是为了避免缓存惊群问题。如果缓存的一批数据的过期时间都设置一样,那么就会出现大量缓存同时过期的情况。这会造成大量的瞬时请求去访问MySQL,对MySQL造成压力。为了避免该问题,可以设置缓存数据的过期时间都是随机的,不集中在某个时间点一起过期。
惊群效应是技术里的术语,指的是突然在某个时间点出了一个故障,导致一大片范围线程、进程、机器都同时被惊动了。
(3)缓存穿透问题的解决方案
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求(布隆过滤器或设置空对象)。如果大量请求的是同一批key(缓存和DB都没数据),则可以对这些key缓存一个空对象。如果大量请求的是不同的key(缓存和DB都没数据),则可以使用布隆过滤器过滤这些key。所以对从DB查出空数据的key缓存空对象,也不一定完全解决缓存穿透问题。
使用布隆过滤器过滤key时,应该是对DB里的所有数据进行添加,在查询缓存前过滤。但是如果在查询缓存前对所有key进行过滤(高风险方案),那么就存在很大风险。因为如果过滤所有key就发生了故障,那么可能会导致所有数据都被误判为没有数据。
所以可以考虑默认采用设置空对象来避免缓存穿透,然后统计这种缓存穿透对DB的请求数。如果请求数超出一定阈值,则说明有大量请求都是不同的key了,设置空对象无法避免,此时再升级为布隆过滤器来避免这种缓存穿透。
6.缓存和数据库双写不一致问题分析
(1)对缓存和数据库双写时发生不一致的场景
(2)缓存和数据库双写时不一致问题的解决方案
(1)对缓存和数据库双写时发生不一致的场景
某用户数据在Redis的缓存已经过期了,此时刚好有两个线程分别并发去对给用户数据进行读取和更新。第一个线程在读取时,发现缓存没有数据,于是就去读库,读完库后会更新缓存。第二个线程在更新时,会先更新数据库,然后再更新缓存。以上两个线程也刚好对应了写缓存的两个场景。
由于这两个线程并发执行,那么就可能出现如下产生不一致的场景:第一个线程首先读库读到了旧值,还没来得及将读到的旧值写入缓存时。第二个线程的新值更新已经完成了写库和写缓存,此时缓存数据是最新的。接着才轮到第一个线程进行写缓存,但是这时候写的数据却是一开始读到的旧值。于是第一个线程写缓存时就把缓存里的最新数据给覆盖了,从而出现数据库里的是新数据,但缓存里的是旧数据,产生了不一致。
(2)缓存和数据库双写时不一致问题的解决方案
有一个简单易行的方案来解决这个问题,就是使用分布式锁让数据库读和写必须是串行化,所以接下来可以对数据库进行读和写时加同一个分布式锁。
7.基于分布式锁保证缓存和数据库双写一致性
用户数据是典型读多写少的数据,可能0.01%是写,99.99%是读,所以进行用户注册和信息更新的操作是极少的。社区平台平时对用户数据大部分都是进行查询,比如分享帖子的详情页、feed流页面就需要查询用户信息。
基于用户数据读多写少的特点,在保证缓存和数据库双写一致性时,就需要注意以下几点。
注意一:不能在读缓存处加锁而在读库时加锁
在用户数据写的地方加锁,由于用户数据具有读多写少的特点,所以几乎不会影响性能。在用户数据读的地方加锁,就需要注意不能在读缓存处加锁,而应该在准备读库时加锁。
注意二:写库和读库加的锁是同一把锁
写数据库的加的锁和准备读数据库的地方加的锁,是同一把锁。
注意三:获取读库的锁后进行双重检查
如果一个线程在获得了读数据库的锁之后,需要进行双重检查,避免再去数据库查一次。因为如果出现大量的请求并发读取某个已过期的用户数据缓存时,此时只会有一个线程获取到锁去查库,然后其他大量的线程都只能在串行化排队。当获取到锁的线程完成读库 + 更新缓存并释放锁后,其他线程就没必要再查库了,否则影响性能。
@Override
public CookbookUserDTO getUserInfo(CookbookUserQueryRequest request) {//由于用户数据具有读多写少的特点,在用户数据读的地方加锁时//需要注意不能在这里的读缓存处加锁,而应该在准备读库时加锁,否则会影响性能Long userId = request.getUserId();//先从缓存中尝试获取用户数据CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}//回源数据库进行读取return getUserInfoFromDB(userId);
}//从缓存中获取不到用户数据需要回源数据库
//首先会获取分布式锁,以防止并发出现两个线程:
//一个线程A在读某用户数据,一个线程B在更新该用户数据,但此时缓存里的该用户数据已过期
//读取该用户数据的线程A先从数据库获取到旧值,紧接着更新该用户数据的线程B马上完成数据库+缓存的新值更新
//之后读取该用户数据的线程A才执行到更新缓存这一步骤,于是使用了旧值去覆盖线程B更新好的新值
private CookbookUserDTO getUserInfoFromDB(Long userId) {//首先获取分布式锁,防止出现两个读写线程造成缓存和DB不一致,这个锁和更新用户数据时用来保证幂等性的锁是同一把锁String userLockKey = RedisKeyConstants.USER_UPDATE_LOCK_PREFIX + userId;boolean lock = false;try {//尝试加锁并且设置锁的超时时间//第一个拿到锁的线程在超时时间内处理完事情会释放锁,其他线程会继续竞争锁//而在这个超时时间里没有获得锁的线程会被挂起并进入队列进行串行等待//如果在这个超时时间外还获取不到锁,排队的线程就会被唤醒并返回falselock = redisLock.tryLock(userLockKey, USER_UPDATE_LOCK_TIMEOUT);} catch(InterruptedException e) {CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}log.error(e.getMessage(), e);throw new BaseBizException("查询失败");}//获取分布式锁超时失败if (!lock) {//并发进来串行排队的线程获取分布式锁超时返回失败后,就重新读缓存(实现"串行等待锁+读缓存"转"串行读缓存")CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}log.info("缓存数据为空,从数据库查询用户数据时获取锁失败,userId:{}", userId);throw new BaseBizException("查询失败");}//获取分布式锁成功try {//1.先读缓存,进行Double Check双重检查,防止并发进来等待锁释放的线程重复去读DB//第一个获取锁的请求设置好缓存后,第二个获取到锁的请求就不需要再去查DB了CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}log.info("缓存数据为空,从数据库中获取数据,userId:{}", userId);String userInfoKey = RedisKeyConstants.USER_INFO_PREFIX + userId;//2.再读DB//此时需要注意://读取某用户数据的线程A首先在这里读到了DB里的旧值,还没来得及接着执行更新读取到的旧值到缓存//然后更新该用户数据的线程B就完成了更新新值的所有操作(更新新值到DB + 缓存)CookbookUserDO cookbookUserDO = cookbookUserDAO.getById(userId);if (Objects.isNull(cookbookUserDO)) {//如果从DB读取到了空数据,那么就对该key设置空值,避免缓存穿透redisCache.set(userInfoKey, CacheSupport.EMPTY_CACHE, CacheSupport.generateCachePenetrationExpireSecond());return null;}CookbookUserDTO dto = cookbookUserConverter.convertCookbookUserDTO(cookbookUserDO);//3.缓存数据,设置缓存时间为2天+随机几小时,避免缓存惊群//此时需要注意://更新某用户数据的线程B已经把缓存里的该用户数据更新到最新了,//读取该用户数据的线程A才执行到这里开始把获取到的旧值更新到缓存,这样就产生数据库和缓存不一致了redisCache.set(userInfoKey, JsonUtil.object2Json(dto), CacheSupport.generateCacheExpireSecond());return dto;} finally {redisLock.unlock(userLockKey);}
}@Override
public SaveOrUpdateUserDTO saveOrUpdateUser(SaveOrUpdateUserRequest request) {//新增或更新用户时先需要获取分布式锁,避免短时间发生多次请求出现重复新增或更新,保证幂等性String userUpdateLockKey = RedisKeyConstants.USER_UPDATE_LOCK_PREFIX + request.getOperator();boolean lock = redisLock.lock(userUpdateLockKey);if (!lock) {log.info("获取锁失败,operator:{}", request.getOperator());throw new BaseBizException("新增/修改失败");}...
}
8.缓存和数据库双写在分布式锁高并发下的优化
当某个冷门用户数据早已过期,但由于热搜等原因突然出现高并发读时,就会出现大量并发线程从缓存里都读不到数据,然后都会尝试进行读库 + 写缓存。也就是有大量并发线程在执行getUserInfoFromDB()方法,出现缓存击穿问题。
这些线程中只会有一个线程获取到锁,而其他并发的线程则产生严重的锁竞争问题。进行锁竞争的线程会串行化排队,第一个获取到锁的线程读库 + 写缓存。后续的线程获取到锁后,通过双重检查就可以直接读缓存了。但是即便有了双重检查,这些排队获取锁的线程还是需要一个个串行获取锁后才能执行。
然而其实只要第一个线程拿到锁,完成读库 + 写缓存后,Redis缓存里就已经有数据了。其他正在串行化排队获取锁的线程,就没必要继续排队去获取锁了。因此只要第一个线程成功完成读库 + 写缓存,其他线程就可以转为无锁情况下的串行读缓存。
为此,可以设置分布式锁的超时时间,超时时间可以参考一个线程完成读库 + 写缓存的时间。这样在超时时间内没有获得锁的线程会等待,超过超时时间内还获取不到锁就会返回false。当这些排队的线程获取分布式锁超时而返回false后,就可以尝试转为无锁串行读缓存了。
9.利用分布式锁自动超时消除串行等待锁的影响
进行加锁时设置锁的超时时间,让排队获取锁的线程自动超时。第一个拿到锁的线程在超时时间内处理完事情会释放锁,其他线程会继续竞争锁。而在这个超时时间里没有获得锁的线程会被挂起并进入队列进行串行等待。如果在这个超时时间外还获取不到锁,排队的线程就会被唤醒并返回false。而获取分布式锁超时失败的线程通过再次读缓存,从而实现无锁串行读缓存。
注意:设置的自动超时时间并不好控制。因此可以参考AQS的做法,获取不到锁的线程先挂起,第一个释放锁的线程就把这些线程全都唤醒执行并发读缓存。AQS是对排队中的线程一个个进行唤醒,需要改造成第一个锁释放后全部排队的线程都唤醒。
private CookbookUserDTO getUserInfoFromDB(Long userId) {//首先获取分布式锁,防止出现两个读写线程造成缓存和DB不一致,这个锁和更新用户数据时用来保证幂等性的锁是同一把锁String userLockKey = RedisKeyConstants.USER_UPDATE_LOCK_PREFIX + userId;boolean lock = false;try {//尝试加锁并且设置锁的超时时间//第一个拿到锁的线程在超时时间内处理完事情会释放锁,其他线程会继续竞争锁//而在这个超时时间里没有获得锁的线程会被挂起并进入队列进行串行等待//如果在这个超时时间外还获取不到锁,排队的线程就会被唤醒并返回falselock = redisLock.tryLock(userLockKey, USER_UPDATE_LOCK_TIMEOUT);} catch(InterruptedException e) {CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}log.error(e.getMessage(), e);throw new BaseBizException("查询失败");}//获取分布式锁超时失败if (!lock) {//并发进来串行排队的线程获取分布式锁超时返回失败后,就重新读缓存(实现"串行等待锁+读缓存"转"串行读缓存")CookbookUserDTO user = getUserFromCache(userId);if (user != null) {return user;}log.info("缓存数据为空,从数据库查询用户数据时获取锁失败,userId:{}", userId);throw new BaseBizException("查询失败");}...
}
10.写少读多的企业级缓存架构设计总结
(1)读多写少场景引入Redis缓存
(2)同步双写实现数据库和缓存强一致性
(3)读多写少场景下的数据库和缓存双写企业级方案
(1)读多写少场景引入Redis缓存
用户数据是典型读多写少的数据,可能0.01%写,99.99%读。读多写少的场景引入Redis缓存是非常有必要的,因为读可能是高并发的。而且读请求没必要都从数据库里读取数据,从缓存中读取数据即可。
(2)同步双写实现数据库和缓存强一致性
关于如何写库和写缓存才能保证数据库和缓存一致性,有两个方案:
方案一:通过异步先写数据库然后根据binlog写缓存实现最终一致性
方案二:通过同步使用分布式锁双写数据库和缓存实现强一致性
这里采用了同步双写,因为比较简单。当进行写时,会将数据库和缓存一起写,并且设置过期时间,以便让缓存留下热数据。当进行读时,每次读取缓存都会自动expire延期,让热数据一直保留在缓存里,冷数据自动过期。当需要读取冷数据时,发现没从缓存中读取到,那么再去读库 + 写缓存。
通过过期时间 -> 实现冷热分离 -> 让冷数据停留在MySQL + 让热数据停留在Redis
(3)读多写少场景下的数据库和缓存双写企业级方案
一.数据库和缓存同步双写
二.缓存实现冷热分离:设置过期时间 + 自动expireTime延期
三.缓存惊群解决方案:随机过期时间
四.缓存穿透解决方案:根据key查库发现不存在,可以缓存空数据
五.数据库缓存强一致性:写库和读库使用同一分布式锁 + 读库前进行双重检查
六.缓存击穿问题:分布式锁 + 消除串行等待锁(读操作的分布式锁设置自动超时)
相关文章:
Redis应用—1.在用户数据里的应用
大纲 1.社区电商的业务闭环 2.Redis缓存架构的典型生产问题 3.用户数据在读多写少场景下的缓存设计 4.热门用户数据的缓存自动延期机制 5.缓存惊群与穿透问题的解决方案 6.缓存和数据库双写不一致问题分析 7.基于分布式锁保证缓存和数据库双写一致性 8.缓存和数据库双写…...
MySQL InnoDB 中的数据页
文章目录 1. 数据库的存储结构概述1.1 表空间(Tablespace)1.2 段(Segment)1.3 区(Extent)1.4 页(Page) 2. InnoDB 数据页的深入解析2.1 数据页的物理结构2.2 数据页中的行存储2.3 数…...
React Fiber
React Fiber 是 React 16 引入的全新重写的协调(Reconciliation)算法的实现,旨在改善 React 的更新机制和性能,尤其是在复杂应用和大量更新的场景下。它使得 React 更加灵活、可调度,能够实现优先级控制和中断更新等特…...
hive 小文件分析
1、获取fsimage文件: hdfs dfsadmin -fetchImage /data/xy/ 2、从二进制文件解析: hdfs oiv -i /data/xy/fsimage_0000000019891608958 -t /data/xy/tmpdir -o /data/xy/out -p Delimited -delimiter “,” 3、创建hive表 create database if not exists…...
大模型运用-Prompt Engineering(提示工程)
什么是提示工程 提示工程 提示工程也叫指令工程,涉及到如何设计、优化和管理这些Prompt,以确保AI模型能够准确、高效地执行用户的指令,如:讲个笑话、java写个排序算法等 使用目的 1.获得具体问题的具体结果。(如&…...
Linux(网络协议和管理)
后面也会持续更新,学到新东西会在其中补充。 建议按顺序食用,欢迎批评或者交流! 缺什么东西欢迎评论!我都会及时修改的! 在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频 王晓春老师的个人空间…...
前端项目打包部署
打包和部署前端项目是将开发环境中的代码转化为生产环境可直接运行的静态文件,并将其部署到服务器上的过程。 # 项目打包 pnpm run build# 上传文件至远程服务器 将本地打包生成的 dist 目录下的所有文件拷贝至服务器的 /usr/share/nginx/html 目录。# nginx.cofig…...
Linux驱动开发(12):中断子系统–按键中断实验
本章我们以按键为例讲解在驱动程序中如何使用中断, 在学习本章之前建议先回顾一下关于中断相关的裸机部分相关章节, 这里主要介绍在驱动中如何使用中断,对于中断的概念及GIC中断控制器相关内容不再进行讲解。 本章配套源码和设备树插件位于“…...
C语言(函数指针与指针函数)
函数指针 定义:函数指针本质上是指针,它是函数的指针(定义了一个指针变量,变量中存储了函数的地 址)。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。这里函数名就代 表入…...
中国计算机学会计算机视觉专委会携手合合信息举办企业交流活动,为AI安全治理打开“新思路”
近期,《咬文嚼字》杂志发布了2024年度十大流行语,“智能向善”位列其中,过去一年时间里,深度伪造、AI诈骗等话题屡次登上热搜,AI技术“野蛮生长”引发公众担忧。今年9月,全国网络安全标准化技术委员会发布了…...
MacOs 日常故障排除troubleshooting
1. 关闭开机自启动 app X macOs 15.1 System settings -> General -> Login Items & Extensions->Open at Login -> Select app X and click -...
ArcGIS字符串补零与去零
我们有时候需要 对属性表中字符串的补零与去零操作 我们下面直接视频教学 下面看视频教学 ArcGIS字符串去零与补零 推荐学习 ArcGIS全系列实战视频教程——9个单一课程组合 ArcGIS10.X入门实战视频教程(GIS思维) ArcGIS之模型构建器(Mod…...
【FLASH、SRAM和DRAM、CISC和RISC、冯诺依曼和哈佛】单片机内存结构的了解
【FLASH、SRAM和DRAM、CISC和RISC、冯诺依曼和哈佛】单片机内存结构的了解 一、单片机概念 单片机:Single-Chip Microcomputer,单片微型计算机,是一种集成电路芯片 1.1RAM里的SRAM和DRAM SRAM(Static Random Access Memory&…...
ionic capacitor JSValueEncodingContainer报错
try to clean the build folder. exit Xcode. upgrade your capacitor core libraries update cocoapods to 1.13.0 do “pod install --repo-udpdate” after that: ionic build --prodnpx cap updatenpx cap syncnpx cap open ios capacitor ios最低版本要求13 [Bug]:…...
pdf merge
在 Ubuntu 22.04 上,你可以使用以下命令行工具来合并多个 PDF 文件: 1. pdftk pdftk 是一个强大的 PDF 工具,支持合并、拆分和其他操作。安装和使用方法如下: sudo apt install pdftk pdftk file1.pdf file2.pdf cat output me…...
【Trouble Shooting】Oracle ADG hung,出现ORA-04021
异常问题: 突然收到告警,ADG实例状态异常。 环境: 版本:Oracle 11.2.0.4.201020 状态:Active Dataguard 问题: 查看Oracle实例alert日志,发现有异常报错: Thu Dec 12 22:15:23 …...
奇怪的知识又增加了:ESP32下的Lisp编程=>ULisp--Lisp for microcontrollers
ESP32下有MicroPython,那么我就在想,有Lisp语言支持吗?答案是果然有!有ULisp,专门为MCU设计的Lisp! 网址:uLisp - Lisp for microcontrollers 介绍:用于微控制器的 Lisp 适用于 Ar…...
什么是CRM系统?CRM系统的功能、操作流程、生命周期
CRM系统作为企业管理和维护客户关系的重要工具,在商业活动中扮演着越来越重要的角色。今天,就让我们一起揭开它的神秘面纱,看看这个“幕后英雄”到底是怎么工作的。 什么是CRM系统? 首先,我们要了解什么是CRM。简单来…...
[每日一练]转换日期格式
#该题目来源于力扣: 1853. 转换日期格式 - 力扣(LeetCode) 题目要求: 表: Days------------------- | Column Name | Type | ------------------- | day | date | ------------------- day 是这个表的主键。给定一个Da…...
LSM Tree 底层设计理念
场景:设计一个海量读写的的kv数据库,优先保证写入速度,但是读取速度也不能很慢 因为海量数据存储,不能使用内存,得存到文件里。 Q:对已经落盘的文件,怎么根据key修改value A:读取文件…...
面向对象设计规则和各类设计模式
面向对象设计(Object-Oriented Design, OOD)是一种软件设计方法论,它使用对象、类、继承、封装、多态等概念来组织代码。面向对象设计的核心目标是提高软件的可维护性、可扩展性和复用性。在面向对象设计中,遵循一定的设计原则和模…...
Artec Leo3D扫描仪在重型机械设备定制中的应用【沪敖3D】
挑战:一家加拿大制造商需要有效的方法,为富于变化且难度较高的逆向工程,快速、安全、准确地完成重型机械几何采集。 解决方案:Artec Leo, Artec Studio, Geomagic for SOLIDWORKS 效果:Artec Leo三维扫描代替过去的手动…...
Linux下socket广播通讯的实现
概念大家都很清楚,不赘述。 广播必然用UDP这套东西。 setsockopt() 函数及其在广播中的应用: 在 C 网络编程中,setsockopt() 函数用于设置套接字选项,这些选项可以控制套接字的各种行为。对于广播通信,我们特别关心…...
Tiptap,: 富文本编辑器入门与案例分析
Tiptap 是一个现代的富文本编辑器,基于 ProseMirror 打造,旨在提供一个灵活且功能强大的文本编辑解决方案。它具有开箱即用的能力,同时也允许开发者根据业务需求进行高度定制化扩展。与传统的富文本编辑器相比,Tiptap 提供了更精细…...
数智读书笔记系列002 埃隆·马斯克传
书名:埃隆马斯克传 作者:【美】沃尔特艾萨克森 译者:孙思远;刘家琦 出版社:中信出版集团 出版时间:2023年9月 ISBN:9787521758399 这本书是关于特斯拉CEO埃隆马斯克的传记,作者…...
linux环境一句话后门
原文地址:linux环境一句话后门 – 无敌牛 欢迎参观我的个人博客:无敌牛 – 技术/著作/典籍/分享等 注意:本文章只做网络安全技术交流使用,切莫用来做坏事。 也可以叫一句话木马,一个意思。 设置监听 回连端口可以…...
django——admin后台管理1
一、admin后台管理 访问url进入: http://127.0.0.1:8000/admin 创建超级管理用户 终端输入以下命令: python manage.py createsuperuser (py36_pingping) E:\django学习\day03-django入门\demo>python manage.py createsuperuser Username: mo…...
QT图形/视图架构详解(一)
场景、视图与图形项 图形/视图架构主要由 3 个部分组成,即场景、视图和图形项,三者的关系如图所示: 场景、视图和图形项的关系 场景(QGraphicsScene 类) 场景不是界面组件,它是不可见的。场景是一个抽象的…...
h5 区分ios和安卓
h5 区分ios和安卓 const systemInfo uni.getSystemInfoSync(); if (systemInfo.platform "ios" || systemInfo.platform "android") {}h5 区分微信小程序与app用条件编译条件编译 js #ifdef MP-WEIXIN #endif...
爬虫基础知识点
最近看了看爬虫相关知识点,做了记录,具体代码放到了仓库,本文仅学习使用,如有违规请联系博主删除。 这个流程图是我使用在线AI工具infography生成的,这个网站可以根据url或者文本等数据自动生成流程图,挺…...
golang 实现简单redis服务3(实现多类型数据结构支持)
redis各种数据类型的工作原理stringlisthashset(集合)zset(有序集合)(思考1):为什么redis使用跳跃表而不是红黑树?(思考2): 都可以范围取值,为什么mysql使用b树不用跳跃表,为什么redis使用跳跃表不用b树? 之前的redis只实现了基本数据string类型的操作,那能不能实现多种数据类…...
【硬件测试】基于FPGA的4ASK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.Verilog核心程序 4.开发板使用说明和如何移植不同的开发板 5.完整算法代码文件获得 1.算法仿真效果 本文是之前写的文章: 《基于FPGA的4ASK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR》 的…...
配置mysqld(读取选项内容,基本配置),数据目录(配置的必要性,目录下的内容,具体文件介绍,修改配置)
目录 配置mysqld 读取选项内容 介绍 启动脚本 基本配置 内容 端口号 数据目录的路径 配置的必要性 配置路径 mysql数据目录 具体文件 修改配置时 权限问题 配置mysqld 读取选项内容 介绍 会从[mysqld] / [server] 节点中读取选项内容 优先读取[server] 虽然服务…...
【roadMap】我转行软件测试的经历
软件测试这行咋样? 如果你简单了解过「软件测试工程师」这个岗位,就会知道它的基本特点: 待遇比开发低,比其他行业高入门丝滑,算是技术岗最简单的一类测试行业有细分领域:功能、性能、自动化… 每个行业…...
回归任务与分类任务应用及评价指标
能源系统中的回归任务与分类任务应用及评价指标 一、回归任务应用1.1 能源系统中的回归任务应用1.1.1 能源消耗预测1.1.2 负荷预测1.1.3 电池健康状态估计(SOH预测)1.1.4 太阳能发电量预测1.1.5 风能发电量预测 1.2 回归任务中的评价指标1.2.1 RMSE&…...
半导体制造全流程
半导体制造是一个极其复杂且精密的过程,主要涉及将硅片加工成功能强大的芯片。以下是半导体制造的全流程概述: 1. 硅材料制备 硅提纯: 使用冶金级硅,进一步提纯为高纯度硅(电子级硅),纯度可达 …...
Mac m2电脑上安装单机Hadoop(伪集群)
1. 引言 本教程旨在介绍在Mac 电脑上安装Hadoop 2. 前提条件 2.1 安装JDK Mac电脑上安装Hadoop,必须首先安装JDK,并配置环境变量(此处不做详细描述) 2.2 配置ssh环境 关闭防火墙 在Mac下配置ssh环境,防止后面启…...
React 第十六节 useCallback 使用详解注意事项
useCallback 概述 1、useCallback 是在React 中多次渲染缓存函数的 Hook,返回一个函数的 memoized的值; 2、如果多次传入的依赖项不变,那么多次定义的时候,返回的值是相同的,防止频繁触发更新; 3、多应用在 父组件为函…...
悬赏任务源码(悬赏发布web+APP+小程序)开发附源码
悬赏任务源码是指一个软件或网站的源代码,用于实现悬赏任务的功能。悬赏任务是指发布方提供一定的奖励,希望能够找到解决特定问题或完成特定任务的人。悬赏任务源码通常包括任务发布、任务接受、任务完成和奖励发放等功能的实现。搭建悬赏任务源码是一个…...
Collection接口
目录 一. Collection基本介绍 二. Collection中的方法及其使用 1. 添加元素 (1) 添加单个元素 (2) 添加另一集合中的所有元素 2. 删除元素 (1) 删除单个元素 (2) 删除某个集合中包含在其他集合中的元素 (3) 保留两个集合中的交集部分, 删除其他元素. 3. 遍历元素 (1) …...
电机驱动模块L9110S详解
电机驱动模块是一种用于控制和驱动电机的设备,它能够将控制信号转化为适合电机操作的电流和电压。通过电机驱动模块,可以实现对电机的速度、方向等参数进行精确控制。 今天我们要介绍的 L9110S 电机驱动适合大学生、工程师、个人DIY、电子爱好者们学习和…...
路由之间是怎么跳转的?有哪些方式?
1. React 路由跳转方式(React Router) 在 React 中,路由跳转通常使用 React Router 来管理。React Router 提供了不同的跳转方式。 <Link> 组件跳转 使用 <Link> 组件来进行路由跳转,它会渲染为一个 HTML <a> …...
AudioSegment 将音频分割为指定长度时间片段 - python 实现
DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 需要更多数据资源和技术解决方案,知识星球: “DataBall - X 数据球(free)” -------------------------------------------------------------…...
双目摄像头标定方法
打开matlab 找到这个标定 将双目左右目拍的图像上传(左右目最好不少于20张) 等待即可 此时已经完成标定,左下角为反投影误差,右边为外参可视化 把这些误差大的删除即可。 点击导出 此时回到主页面,即可看到成功导出 Ca…...
题解 - 莱布尼茨三角形
题目描述 世界上著名的莱布尼茨三角形如图所示,请编程输出图中排在第n行从左边数第m个位置上的数。 输入 共一行,有二个整数N 和M(N<15),两数间用空格隔开。 输出 共一行,有二个整数,两数间用“/”隔开ÿ…...
低代码开发:企业数智化转型中的关键角色与实践案例分析
引言 随着信息技术的飞速发展,企业数智化转型已成为提升竞争力和实现可持续发展的必经之路。在这一过程中,低代码开发平台作为一种新兴的开发模式,正逐渐成为企业数智化转型的关键角色。本文将探讨低代码开发在企业数智化转型中的作用&#x…...
【ubuntu24.04】PDFMathTranslate 本地PDF翻译GPU部署
https://huggingface.co/spaces/reycn/PDFMathTranslate-Docker排不上号官方都是要安装包,感觉可以本地试着源码部署一下, http://localhost:7860/官方是这个端口,但是我本地启动是:5000IDEA 里本地 backend启动效果 GUI 是监听7860的...
基于Spring Boot + Vue的摄影师分享交流社区的设计与实现
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有实…...
高空坠物识别
目录 一、回答二、其他参考 prompt 有没有识别高空坠物的github项目一、回答 根据您的请求,以下是一些在GitHub上可以找到的关于识别高空坠物的项目: 1、overthecity 这是一个监控高空抛物的云端服务项目,它利用Edison的计算能力对高空抛物…...
22. 正则表达式
一、概述 正则表达式(regular expression)又称 规则表达式,是一种文本模式(pattern)。正则表达式使用一个字符串来描述、匹配具有相同规格的字符串,通常被用来检索、替换那些符合某个模式(规则&…...