1.��redisԴ��
2.Redis高并发缓存架构性能优化实战
3.一线大厂都在用的大厂异地多活的 5 种解决方案!
4.Golang后端大厂面经!源源码
��redisԴ��
秒杀功能在电商网站中应用广泛,实现方式通常采用PHP与Redis结合。源源码以下为秒杀功能实现步骤:
第一步:安装Redis。大厂确认与您的源源码彩虹简约模板源码PHP版本匹配的Redis扩展。
第二步:下载并解压Redis,大厂将php_redis.dll和php_redis.pdb文件拷贝到PHP的源源码ext目录下。
第三步:编辑php.ini文件,大厂在Apache目录下的源源码php.ini文件中加入扩展配置,确保php_igbinary.dll在php_redis.dll前面,大厂以确保正确加载。源源码
第四步:重启Apache后,大厂通过phpinfo()检查Redis扩展是源源码否成功安装。
第五步:在ThinkPHP5.0中配置Redis。大厂创建Redis.php文件在extend下的module文件夹内。
处理秒杀的核心在于防止超库存购买,实现方式为在秒杀类中进行数据预处理。使用商品表、日志表和订单表进行测试。
秒杀入口代码示例如下:
至此,秒杀功能的基础实现已就绪。更多信息资源,如PHP大厂面试文档、视频资料及精彩文章,欢迎访问:
四年精华PHP技术文章整理合集——PHP框架篇
四年精华PHP技术文合集——微服务架构篇
四年精华PHP技术文合集——分布式架构篇
四年精华PHP技术文合集——高并发场景篇
四年精华PHP技术文章整理合集——数据库篇
Redis高并发缓存架构性能优化实战
Redis高并发缓存架构性能优化实战场景1: 中小型公司Redis缓存架构以及线上问题实战
线程A在master获取锁之后,master在同步数据到slave时,master突然宕机(此时数据还没有同步到slave),然后slave会自动选举成为新的master,此时线程B获取锁,结果成功了,这样会造成多个线程获取同一把锁
解决方案
网上说RedLock能解决分布式锁失效的问题。对于RedLock实现原理是: 超过半数Redis节点加锁成功之后才能算成功,否则返回false,和Zookeeper的"ZAB"原理很类似,而且与Redis Cluster集群中解决脑裂问题的方案类似,但是RedLock方案有很大的弊端,也就是会造成Redis可用性的延迟,众所周知,智能名片定制源码Redis的AP(可用性+分区容忍性)机制,假如把Redis变成CP(一致性+分区容忍性),这样肯定会牺牲一定的可用性,与Redis初衷不符合,也就是说还不如使用Zookeeper。
Zookeeper具备CP机制以及实现了ZAB,能够确保某一个节点宕机,也能保证数据一致性,而且效率会比Redis高很多,更适合做分布式锁
场景2: 大厂线上大规模商品缓存数据冷热分离实战问题: 在高并发场景下,一定要把所有的缓存数据一直保存在缓存不让其失效吗?
虽然一直缓存所有数据没什么大问题,但是考虑到如果数据太多,就会一直占用缓存空间(内存资源非常宝贵),并且数据的维护性也是需要耗时的.
解决方案
对缓存数据做冷热分离。在查询数据时,我们只需要在查询代码中再次更新过期时间,这样就能保证热点数据一直在缓存中,而不经常访问的数据过期了就自动从缓存中删除。
流程分析
假如一个热点数据每天访问特别高,不停的查询该数据,每次查询时再次更新过期时间,那么在这个过期时间之内只要有人访问就会一直存在缓存中,这样就保证热点商品数据不会因为过期时间而从缓存中移除;
而对于不经常访问的冷门数据到了过期时间就可以自动释放了,同时也释放除了一部分缓存空间,而且当再次访问冷门数据的时候,从数据库拿到的永远是最新的数据,也减少了维护成本。
场景3: 基于DCL机制解决热点缓存并发重建问题实战DCL(双重检测锁)
问题: 冷门数据突然变成了热门数据,大量的请求突发性的对热点数据进行缓存重建导致系统压力暴增
解决方案
最容易想到的就是加锁
DCL机制。先查一次,缓存有数据就直接返回,没有数据,就加锁,在锁的代码块中再次先查询缓存。这样锁的目的就是为了当第一次缓存从数据库查询更新到缓存中,代码块执行完,其他线程再次进来,此时缓存中就已经存在数据了,长兴资源码查询这样就减少了查询数据库的次数
public?Product?get(Long?productId)?{ Product?product?=?null;String?productCacheKey?=?RedisKeyPrefixConst.PRODUCT_CACHE?+?productId;//DCL机制:第一次先从缓存里查数据product?=?getProductFromCache(productCacheKey);if?(product?!=?null)?{ return?product;}//加分布式锁解决热点缓存并发重建问题RLock?hotCreateCacheLock?=?redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX?+?productId);hotCreateCacheLock.lock();//?这个优化谨慎使用,防止超时导致的大规模并发重建问题//?hotCreateCacheLock.tryLock(1,?TimeUnit.SECONDS);try?{ //DCL机制:在分布式锁里面第二次查询product?=?getProductFromCache(productCacheKey);if?(product?!=?null)?{ return?product;}//RLock?productUpdateLock?=?redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX?+?productId);RReadWriteLock?productUpdateLock?=?redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX?+?productId);RLock?rLock?=?productUpdateLock.readLock();//加分布式读锁解决缓存双写不一致问题rLock.lock();try?{ product?=?productDao.get(productId);if?(product?!=?null)?{ redisUtil.set(productCacheKey,?JSON.toJSONString(product),genProductCacheTimeout(),?TimeUnit.SECONDS);}?else?{ //设置空缓存解决缓存穿透问题redisUtil.set(productCacheKey,?EMPTY_CACHE,?genEmptyCacheTimeout(),?TimeUnit.SECONDS);}}?finally?{ rLock.unlock();}}?finally?{ hotCreateCacheLock.unlock();}return?product;}场景4: 突发性热点缓存重建导致系统压力暴增问题: 假如当前有w个线程没有拿到锁正在排队,这种情况只能等到获取锁的线程执行完代码释放锁后,那排队的w个线程才能再次竞争锁。这里需要关注的问题点就是又要再次竞争锁,意味着线程竞争锁的次数可能最少>1,频繁的竞争锁对Redis性能也是有消耗的,有没有更好的办法让每个线程竞争锁的次数尽可能减少呢?
解决方案
可以通过tryLock(time,TimeUnit)先让所有线程尝试获取锁
假如获取锁的线程执行数据库查询然后将数据更新到缓存所需要的时间为1s,那么当其他线程获取锁时间结束后,会解除阻塞状态直接往下执行,然后再次查询缓存的时候发现缓存有数据了就直接返回。
这样设计的好处就是把分布式锁在某些特定的场景使其"串行变并发",不过这个优化需要谨慎使用,防止超时导致的大规模并发重建问题。毕竟没有任何方案是完全解决问题的,主要是根据公司业务而定.
场景5: 解决大规模缓存击穿导致线上数据库压力暴增缓存击穿/缓存失效:可能同一时间热点数据全部过期而造成缓存查不到数据,请求就会从数据库查询,高并发情况下会导致数据库压力
解决方案
对于这个场景,可以给数据设置过期时间时,不要将所有缓存数据的过期时间设置为相同的过期时间,最好可以给每个数据的过期时间设置一个随机数,保证数据在不同的时间段过期。
代码案例
private?Integer?genProductCacheTimeout()?{ ?//加随机超时机制解决缓存批量失效(击穿)问题?return?PRODUCT_CACHE_TIMEOUT?+?new?Random().nextInt(5)?*??*?;}场景6: 黑客工资导致缓存穿透线上数据库宕机缓存穿透: 如果黑客通过脚本文件不停的传一些不存在的参数刷网站的接口,而这种垃圾参数在缓存和数据库又不存在,这样就会一直地查数据库,最终可能导致数据库并发量过大而卡死宕机。
解决方案
网关限流。Nginx、Sentinel、Hystrix都可以实现
代码层面。可以使用多级缓存,比如一级缓存采用布隆过滤器,二级缓存可以使用guava中的Cache,三级缓存使用Redis,为什么一级缓存使用布隆过滤器呢,其结构和bitmap类似,用于存储数据状态,能存大量的聚缘网源码key
布隆过滤器
布隆过滤器就是一个大型的位数组和几个不一样的无偏Hash函数.当布隆过滤器说某个值存在时,这个值可能不存在,当说不存在时,那就肯定不存在。
场景7: 大V直播带货导致线上商品系统崩溃原因分析问题: 这种场景可能是在某个时刻把冷门商品一下子变成了热门商品。因为冷门的数据可能在缓存时间过期就删除,而此时刚好有大量请求,比如直播期间推送一个商品连接,假如同时有几十万人抢购,而缓存没有的话,意味着所有的请求全部达到了数据库中查询,而对于数据库单节点支撑并发量也就不到1w,此时这么大的请求量,肯定会把数据库整宕机(这种场景比较少,但是小概率还是会有)
解决方案
可以通过tryLock(time,TimeUnit)先让所有线程尝试获取锁
假如获取锁的线程执行数据库查询然后将数据更新到缓存所需要的时间为1s,那么当其他线程获取锁时间结束后,会解除阻塞状态直接往下执行,然后再次查询缓存的时候发现缓存有数据了就直接返回。
这样设计的好处就是把分布式锁在某些特定的场景使其"串行变并发",不过这个优化需要谨慎使用,防止超时导致的大规模并发重建问题。毕竟没有任何方案是完全解决问题的,主要是根据公司业务而定.
场景8: Redis分布式锁解决缓存与数据库双写不一致问题实战解决方案
重入锁保证并发安全。通常说在分布式锁中再加一把锁,锁太重,性能不是很好,还有优化空间
分布式读写锁(ReadWriteLock),实现机制和ReentranReadWriteLock一直,适合读多写少的场景,注意读写锁的key得一致
使用canal通过监听binlog日志及时去修改缓存,但是引入中间件,增加系统的维护度
Lua脚本设置读写锁
local?mode?=?redis.call('hget',?KEYS[1],?'mode');if?(mode?==?false)?then?redis.call('hset',?KEYS[1],?'mode',?'read');?redis.call('hset',?KEYS[1],?ARGV[2],?1);?redis.call('set',?KEYS[2]?..?':1',?1);?redis.call('pexpire',?KEYS[2]?..?':1',?ARGV[1]);redis.call('pexpire',?KEYS[1],?ARGV[1]);?return?nil;?end;?if?(mode?==?'read')?or?(mode?==?'write'?and?redis.call('hexists',?KEYS[1],?ARGV[3])?==?1)?then?local?ind?=?redis.call('hincrby',?KEYS[1],?ARGV[2],?1);?local?key?=?KEYS[2]?..?':'?..?ind;redis.call('set',?key,?1);?redis.call('pexpire',?key,?ARGV[1]);?redis.call('pexpire',?KEYS[1],?ARGV[1]);?return?nil;?end;return?redis.call('pttl',?KEYS[1]);ReadWriteLock代码案例
@Transactionalpublic?Product?update(Product?product)?{ ?Product?productResult?=?null;?//RLock?productUpdateLock?=?redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX?+?product.getId());?RReadWriteLock?productUpdateLock?=?redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX?+?product.getId());?//?添加写锁?RLock?writeLock?=?productUpdateLock.writeLock();?//加分布式写锁解决缓存双写不一致问题?writeLock.lock();?try?{ ?productResult?=?productDao.update(product);?redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE?+?productResult.getId(),?JSON.toJSONString(productResult),?genProductCacheTimeout(),?TimeUnit.SECONDS);}?finally?{ ?writeLock.unlock();}?return?productResult;}public?Product?get(Long?productId)?{ Product?product?=?null;String?productCacheKey?=?RedisKeyPrefixConst.PRODUCT_CACHE?+?productId;//从缓存里查数据product?=?getProductFromCache(productCacheKey);if?(product?!=?null)?{ return?product;}//加分布式锁解决热点缓存并发重建问题RLock?hotCreateCacheLock?=?redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX?+?productId);hotCreateCacheLock.lock();//?这个优化谨慎使用,防止超时导致的大规模并发重建问题//?hotCreateCacheLock.tryLock(1,?TimeUnit.SECONDS);try?{ product?=?getProductFromCache(productCacheKey);if?(product?!=?null)?{ return?product;}//RLock?productUpdateLock?=?redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX?+?productId);RReadWriteLock?productUpdateLock?=?redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX?+?productId);//?添加读锁RLock?rLock?=?productUpdateLock.readLock();//加分布式读锁解决缓存双写不一致问题rLock.lock();try?{ product?=?productDao.get(productId);if?(product?!=?null)?{ redisUtil.set(productCacheKey,?JSON.toJSONString(product),genProductCacheTimeout(),?TimeUnit.SECONDS);}?else?{ //设置空缓存解决缓存穿透问题redisUtil.set(productCacheKey,?EMPTY_CACHE,?genEmptyCacheTimeout(),?TimeUnit.SECONDS);}}?finally?{ rLock.unlock();}}?finally?{ hotCreateCacheLock.unlock();}return?product;}场景9: 大促压力暴增导致分布式锁串行争用问题优化解决方案
可以采用分段锁,和JDK7的ConcurrentHashMap的实现原理很类似,将一个锁,分成多个锁,比如lock,分成lock_1、lock_2...
然后将库存平均分摊到每把锁,免费看源码软件这样做的目的是分摊分布式锁的压力,本来只有一个锁,意味着所有的线程进来只能一个线程获取到锁,如果分摊为把锁,那么同一时间可以有个线程同时获取到锁对同一个商品进行操作,也就意味着在同等环境下,分段锁的效率比只用一个锁要高得多
场景: 利用多级缓存解决Redis线上集群缓存雪崩问题缓存雪崩: 缓存支撑不住或者宕机,然后大量请求涌入数据库。
解决方案
网关限流。Nginx、Sentinel、Hystrix都可以实现
代码层面。可以使用多级缓存,比如一级缓存采用布隆过滤器,二级缓存可以使用guava中的Cache,三级缓存使用Redis,为什么一级缓存使用布隆过滤器呢,其结构和bitmap类似,用于存储数据状态,能存大量的key
场景: 一次微博明显热点事件导致系统崩溃原因分析问题: 比如微博上某一天某个明星事件成为了热点新闻,此时很多吃瓜群众全部涌入这个热点,如果并发每秒达到几十万甚至上百万的并发量,但是Redis服务器单节点只能支撑并发w而已,那么可能因为这么高的并发量导致很多请求卡死在那,要知道我们其他业务服务也会用到Redis,一旦Redis卡死,就会影响到其他业务,导致整个业务瘫痪,这就是典型的缓存雪崩问题
解决方案: ?参考场景
场景: 大厂对热点数据处理方案解决方案
如果按照场景的方案去实现,需要考虑数据一致性问题,这样就不得不每次对数据进行增加、删除、更新都要立马通知其他节点更新数据,能做到及时更新数据的方案可能就是:Redis发布/订阅、MQ等
虽然说这些方案实现也可以,但是不可避免的我们需要再维护相关的中间件,提高了维护成本
目前大厂对于热点数据专门会有一个类似于热点缓存系统来维护,所有的web应用只需要监听这个系统,只要有热点时,直接更新缓存,这样既能减少代码耦合,还能更好的维护热点数据。
那么热点数据来源怎么获取呢?可以在设计查询的接口使用类似于Spring AOP的方式,每次查询就把数据传送到热点数据,一般大厂都会有数据分析岗位,根据热点规则将数据分类
原文:/post/
一线大厂都在用的异地多活的 5 种解决方案!
有状态服务
后台服务分为有状态和无状态两类。无状态应用在高可用方面相对简单,通常通过负载均衡或代理解决。有状态服务则需要通过磁盘或内存保存状态,如MySQL、Redis等。JVM内存状态维护的生命周期较短,不作为主要讨论对象。
高可用解决方案
高可用发展经历了几个阶段,包括冷备、双机热备、Active/Standby模式和双机互备。冷备通过文件拷贝实现快速备份,但不适用于现代场景,因为需要考虑备份与服务的兼容性与恢复时间。双机热备一边备份一边提供服务,但恢复时仍需停机。Active/Standby模式下,主节点提供服务,从节点作为备份,通过同步数据在故障发生时切换工作。双机互备在服务器层面实现主从切换,减少资源浪费,适用于读写分离的场景。
同城双活与异地多活
同城双活与双机热备在原理上相似,但异地多活考虑了更远距离的备份机房,以抵御更大范围的灾难。异地多活通过部署前端入口和应用,实现流量切换,降低用户体验影响。星状异地多活结构消除了城市下线对数据的影响,通过LoadBalance重新分配流量,简化数据同步问题。
电商复杂场景
对于电商等复杂业务场景,无法简单地进行数据分片。淘宝采取了单元化分层架构,将业务拆分为交易单元和中心单元,实现双向同步与单向同步,确保中心单元承担复杂业务场景,业务单元则进行弹性伸缩和容灾。
思考与问题
对于异地多活部署的饿了么服务,如何处理买家在多城市交汇地点的数据获取?当前业务模块中哪些可以实现多活部署?是否所有业务都需要做多活?
Golang后端大厂面经!
大家好,我是阳哥,专注于Go语言的学习经验分享和就业辅导。以下是关于Go语言后端大厂面经的更新内容,来自一位同学的投稿,主要涉及Go语言相关知识、微服务和Redis。
让我们一起深入探讨Go语言的特性:
**Slice扩容机制**?为什么不一直用2倍扩容?
从Go 1.版本开始,slice扩容机制采用了更加平滑的方式,不再固定使用作为临界点,而是将threshold设定为。当slice容量小于threshold时,每次扩容为原来的两倍;当容量大于threshold时,每次增加(oldcap + 3*threshold)*3/4的容量。这种策略避免了频繁的大扩容,减少了内存浪费。
**Go内存分配机制**?多级缓存?组件?
Go的内存管理高度自动化,内存释放不直接归还给操作系统,而是尽量复用,减少与内核态的切换。每一个线程M独享一个mcache,在申请内存时优先从mcache中获取,不足时向mcentral获取,再不足则向mheap申请,最后向操作系统请求内存(mcache->mcentral->mheap->OS)。高效之处在于mcache、mcentral和mheap通过span class实现分类,减少锁的竞争,提升性能。
**Go垃圾回收 GC原理
**Go的垃圾回收采用三色标记法和混合写屏障。三色标记法将对象标记为白色、灰色或黑色。白色对象为不确定状态;黑色对象为存活状态;灰色对象为存活状态,但其子对象还需处理。标记过程首先将所有对象加入白色集合,然后取出灰色对象,遍历其子对象,加入灰色集合,最后黑色集合对象为存活,白色集合对象为需要清理的对象。这种方法避免了引用修改导致的标记失效问题,显著降低了垃圾回收的停顿时间。
**CSP并发模型
**在并发编程中,使用channel进行通信,实现了通过通信共享内存的CSP思想。这种思想提供高度的灵活性,但也可能引发死锁问题。
**互斥锁和读写锁
**Go提供了互斥锁和读写锁两种锁机制,互斥锁保证一个goroutine获取锁后,其他goroutine必须等待释放;读写锁允许多个goroutine获取读锁执行读操作,但写锁获取后则禁止其他goroutine获取任何锁。
**sync包
**sync包提供了丰富的并发工具,包括waitgroup、sync.map、sync.Lock、sync.RWLock和sync.Pool等。sync.map使用read和dirty两个map实现读写分离,减少锁的使用,提高并发效率。
**协程池
**sync.Pool用于管理可重用的对象池,减少内存分配和回收的开销。线程池则提供了一种高效管理线程的技术,包括sync.Pool在内的协程池能够更好地管理和复用协程,提高系统性能和资源利用率。
**防止Go协程泄露/未关闭
**通过管道channel通知关闭,使用waitgroup监控协程退出,使用context上下文控制协程的生命周期。
**select的用法
**select语句的执行顺序是随机的,这为并发控制提供了灵活的手段。
**map的键
**可以是实现了比较操作的类型,如基本数据类型、数组等;结构体作为键时,所有字段必须实现比较操作。
**微服务
**微服务之间通信方式包括RPC、gRPC、HTTP等。gRPC是一种高性能的RPC框架,基于HTTP/2,使用Protocol Buffers作为序列化机制,提供高效、跨语言和跨平台的通信能力。与JSON相比,Protobuf在性能、可读性和跨语言支持上具有优势。
**Gorm优势
**Gorm是一个简洁易用的ORM框架,支持多种数据库,具有自动迁移、事务支持等特性,简化了数据库操作。
**Redis数据结构
**Redis提供了多种数据结构支持,包括字符串、集合、有序集合、哈希表、列表等。其中,哈希表和有序集合底层使用了skiplist和ziplist,满足特定条件时使用ziplist,否则使用skiplist;列表底层使用快速列表(quicklist),快速列表由zipList和linkedList混合组成,提供高效的插入、删除和访问操作。
**zset和set介绍
**zset和set都是Redis中的数据结构,zset是有序集合,底层使用了跳表和哈希表;set则是无序集合,底层使用哈希表。
**压缩列表介绍
**压缩列表通过连续的内存块存储数据,减少了元数据开销,提供了高效的数据插入、删除和访问操作。
**渐进式rehash
**渐进式rehash是Redis在进行哈希表扩容时采用的策略,通过逐步迁移数据来避免一次迁移对系统性能的影响,保持数据一致性。
更多面经分享
**以下面经同样精彩,希望能帮助大家在求职路上更加顺利:
1. 噢耶!字节跳动后端Offer,拿到了!
2. 一天约了4个面试,复盘一下面试经历和薪资范围
3. 避免失业和岁危机,把这份百度3面的面经分享出来
4. 最新社招面经分享:字节、米哈游、富途、猿辅导
如果你对这些内容感兴趣,欢迎关注我的公众号:程序员升职加薪之旅,也请大家关注我的账号,点赞、留言、转发。你的支持是我持续分享的动力!