如果要存IP地址,用什么数据类型比较好?

典型回答 IP地址,分为IPV4和IPV6,通常IPv4地址的地址格式为nnn.nnn.nnn.nnn,如192.0.2.235,而IPv6的地址 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,例如2001:0db8:86a3:08d3:1319:8a2e:0370:7344 要回答好这个问题,需要区分不同的情况,因为IPV4和IPV6是不一样的。 ✅什么是IPV6?和IPV4有什么区别? 先总结一下,然后再展开说。 对于IPV4和IPV6来说,字符串格式是最直观和灵活的,适合需要展示或直接操作原始地址的场景。 对于IPV4和IPV6来说,如果有些场景进行大量的位运算,或者对空间要求更加极致,那么二进制是一个好的选择 **对于IPV4,如果考虑性能和空间的权衡,那么建议使用整数格式存储。**因为他是一个折中方案,在效率和空间之间相对平衡。并且不至于很复杂。 **而对于IPV6,如果考虑性能和空间的权衡,那么建议使用二进制格式存储。**因为IPV6的整数存储方案比较复杂,需要分多个段,反而会更加复杂一些。 IPV4 存储 想要存储一个IPV4的地址,有几种办法,分别是字符串存储、32位整数存储。 字符串 将IP地址直接作为字符串存储,例如"192.168.1.1"这个ip地址,直接在数据库中就存储为varchar(15)类型,存储内容就直接是"192.168.1.1"。 字符串存储的这种方式的好处就是比较直观。但是也有缺点,那就是在进行地址比较、排序或者范围查询时可能不够高效。 IPv4地址由四个十进制数表示,每个数取值范围从0到255,中间用三个点分隔。最短的IPv4地址形式是"1.1.1.1",共7个字符;最长的形式是"255.255.255.255",共15个字符。如果每个字符占用1字节(在ASCII编码下),那么存储IPv4地址需要7到15字节的存储空间。 整数存储 IPv4地址可以采用32位的无符号整数(UNSIGNED INT)来存储。这种方法可以节省空间,并使得比较和排序操作更加高效。可以通过将IP地址的每个字节转换为整数并组合成一个单一的32位整数来实现。 将每个十进制段转换为8位的二进制数,然后合并成一个32位的整数。例如,“192.168.1.1” 转换为整数可以表示为 3232235777(在二进制下为 11000000.10101000.00000001.00000001,合并为11000000101010000000000100000001,转换为十进制后为3232235777)。 IPv4地址总共有32位,可以存储为一个32位的整数。无论IP地址的实际值是多少,都恒定使用4字节的存储空间。 相比于用字符串需要7-15个字节,而使用整数存储只需要4个字节,空间上是大大减少了的。 在MySQL中,对于IPv4地址,可以使用INET_ATON函数来转换IP地址为一个整数。例如: 1 SELECT INET_ATON('192.168.1.1'); 以上SQL将返回一个整数:3232235777 二进制存储 IP地址列也可以使用BINARY(4)或VARBINARY(4)数据类型来定义,以确保每个IP地址正好使用4字节存储。 1 2 3 4 CREATE TABLE binary_ipv4_addresses ( id INT AUTO_INCREMENT PRIMARY KEY, ip_address BINARY(4) ); 将IPv4地址转换为二进制格式并插入到表中时,可以使用MySQL的INET_ATON函数将点分十进制的IP地址转换为一个整数,然后使用UNHEX函数将该整数转换为二进制形式。 假设你想插入IP地址"192.168.1.1",可以这样操作: 1 2 INSERT INTO binary_ipv4_addresses (ip_address) VALUES (UNHEX(LPAD(HEX(INET_ATON('192.168.1.1')), 8, '0'))); 查询并显示二进制格式存储的IPv4地址时,可以使用HEX函数将二进制数据转换回十六进制字符串,然后用INET_NTOA函数将其转换为人类可读的点分十进制格式: ...

March 22, 2026 · 1 min · santu

库存扣减如何避免超卖和少卖?

典型回答 所谓"超卖"指的就是商品卖多了,一般我们在商品扣减库存的时候,都会先判断库存够不够,如果够在进行扣减,不够则直接返回下单失败。 但是,如果在高并发场景中,可能存在以下情况: 当有两个并发线程,同时查询库存,这时数据库中库存剩余1,所以两个线程都得到1的库存,然后经过库存校验之后分别开始进行库存扣减,最终导致库存被扣减成负数。 以上,就是一个典型的高并发情况下的超卖问题。 之所以会发生以上问题,主要是因为并发导致的,所以,解决超卖的问题本质上是解决并发问题。以上问题,最终就是要实现库存扣减过程中的原子性和有序性。 原子性:库存查询、库存判断以及库存扣减动作,作为一个原子操作,过程中不会被打断,也不会有其他线程执行。 有序性:多个并发操作需要排队执行。 数据库扣减 数据库中进行库存扣减是最容易想到的方案,这个方案实现起来非常简单。 在扣减过程中,想要保证原子性和有序性,我们可以采用加锁的方式,无论是悲观锁、还是乐观锁都可以实现的。 但是,如果使用悲观锁来实现的话,就会导致很多请求被迫阻塞并且排队,那么如果并发请求量很大的话,就可能直接把数据库给拖垮了。 如果是乐观锁的话,可以用版本号的方式来控制有序执行,但是这个问题在于高并发场景中会存在大量的失败,而且高并发场景中也不适合使用乐观锁,因为乐观锁在update的过程中也是需要加行级锁的,也是会出现阻塞的情况。 那么,在库存扣减时,如果不加锁可以吗? 其实是可以的,我们就借助数据库自己执行引擎的顺序执行机制,只要保证库存不要扣减成负数就行了,那么可行的方案是通过SQL语句就能控制,如: 1 2 3 update inventory set quantity = quantity - #{count} where sku_id='123' and quantity >= #{count} 也就是说,如果上述SQL可以执行成功的话,是可以确保库存余量大于等于0的,这就避免了超卖的发生。 但是这个方案好吗?其实是不好的。 因为这个方案本质上和乐观锁的方案缺点是一样的,都是完全依赖数据库,并且高并发情况下,多个线程同时update inventory 的时候会发生阻塞,不仅会很慢,还会把数据库拖垮的。详见: ✅数据库CPU被打满排查过程 正常来说,MySQL的热点行更新最多也就抗200-300的并发更新,如果想要抗的更多,要么就是提升硬件水平,要么就是做一些技术改造,比如inventory hint的方式。 ✅MySQL怎么做热点数据高效更新? ✅高并发的库存系统,在数据库扣减库存,怎么实现? 那么,不用数据库扣减的话,可以用什么呢?答案是可以借助缓存的扣减。 Redis扣减 我们可以基于Redis做库存扣减的,借助Redis的单线程执行的特性,再加上Lua脚本执行过程中的原子性保障,我们可以在Redis中通过Lua脚本进行库存扣减。 在Redis中,使用以下Lua脚本: 1 2 3 4 5 6 7 8 9 10 11 12 13 local key = KEYS[1] -- 商品的键名 local amount = tonumber(ARGV[1]) -- 扣减的数量 -- 获取商品当前的库存量 local stock = tonumber(redis.call('get', key)) -- 如果库存足够,则减少库存并返回新的库存量 if stock >= amount then redis.call('decrby', key, amount) return redis.call('get', key) else return "INSUFFICIENT STOCK" end 先从Redis中取出当前的剩余库存,然后判断是否足够扣减,如果足够的话,就进行扣减,否则就返回库存不足。 ...

March 22, 2026 · 1 min · santu

用了本地消息表的方案,如果下游执行失败了上游如何回滚?

典型回答 首先,我们需要知道,本地消息表的方案并不适合用在这种需要回滚的场景,而是适合用在哪种不需要回滚的场景。什么场景不需要回滚呢? 举个例子,我们都知道,用户下单之后,会给用户创建一个运费险,那么这个场景,一般运费险的投保过程前置条件就是下单成功,而且,下单的时候给用户表达了有运费险,那么就意味着,一旦下单成功了,就必须要投保成功。不能因为投保未成功而导致订单回滚。 所以,这就是典型的本地消息表的,或者是事务消息适合的场景!!!即不回滚,必须成功。 但是,如果面试官问了这个问题,你回答了上面的内容,他还是问你,我就要回滚,该咋做呢? ✅如何基于本地消息表实现分布式事务? 这是我们讲过的本地消息表的方案,我们都知道,这种引入MQ的机制,就意味着整个过程不可能做成一种自动回滚的机制了,因为MQ就代表着两个系统已经解耦了,互相没关系了。 首先,下游服务处理消息时发生业务失败,如网络异常、数据校验不通过、依赖服务不可用等等,需要先明确返回一个失败的响应,这样MQ就会继续投递这个消息。所以,失败的时候,我们优先考虑的是重试,而不是回滚。 之后如果多次重试都不成功,那么就可以借助死信队列,把这条失败的消息放进死信队列中,然后上游可以再监听这个死信队列的消息,做本地事务的回滚。当然,为了避免出现错误,回滚前建议反查一下下游的接口,避免实际成功了的情况。 PS:其实,我强烈不太建议这个所谓的"方案",我相信如果去问ai,他肯定给这样的方案,看上去挺好的,但是实际上就很麻烦,因为这么做了之后,两个系统之间的耦合就很严重,互相依赖消息,互相依赖接口查询。而且一旦有多个消息监听者都要去操作,比如下单后要依次运费险投保、给用户发消息、给用户加积分等等,就很麻烦,如果部分成功,部分失败,又怎么处理呢?实际上系统根本没法处理。。。。但是如果面试官一定要,那有没有更好的方案了。当然,对于很多面试官来说,你不说他也不知道有这些问题,那就给他这个他想听的答案好了。 要我说实际可行的方案是什么?那就是人工介入,当一个消息多次投递都不成功的话,记录一下,然后人工介入,或者是依靠对帐机制做准实时对账,发现不一致的情况人工介入。 这种场景发生的概率很低,一旦发生了可能就是有一些特殊的原因,可能需要人工介入才能解决。

March 22, 2026 · 1 min · santu

线上接口如果响应很慢如何去排查定位问题呢?

典型回答 想要定位一个接口响应慢,有一个神器必备,那就是阿里巴巴推出的arthas,很快的就能定位到接口RT慢的问题,具体有一个案例,大家可以看下: ✅RT飙高问题排查过程 使用Arthas定位接口慢的步骤大致如下: **1、首先就是通过各种监控系统,发现慢的现象或者问题。**我们明确的知道了具体是哪个接口慢了。然后才能开始分析。 **2、安装arthas,**arthas就是一个命令工具,他通过字节码插桩的方式来统计接口耗时的,很多公司的生产环境也都是可以用的,包括我司的生产环境都是可以用的,目前没发现什么副作用。 1 curl -L http://start.alibaba-inc.com/install.sh | sh 以上命令即可安装。 **3、运行,**比较简单,执行命令就行: 1 sh as.sh 4、查看接口耗时,这个就需要借助arthas的trace命令了,详见 https://arthas.aliyun.com/ 官网,trace命令的主要作用就是查看方法内部调用路径,并输出方法路径上的每个节点上耗时。 参数说明如下: 比如直接查看一次真实请求的耗时情况(例子来自官网,大家可以自己实践一下): 1 2 3 4 5 6 7 8 9 10 $ trace demo.MathGame run Press Q or Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 28 ms. `---ts=2019-12-04 00:45:08;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69 `---[0.617465ms] demo.MathGame:run() `---[0.078946ms] demo.MathGame:primeFactors() #24 [throws Exception] `---ts=2019-12-04 00:45:09;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69 `---[1.276874ms] demo.MathGame:run() `---[0.03752ms] demo.MathGame:primeFactors() #24 [throws Exception] 如果方法调用的次数很多,那么可以用-n参数指定捕捉结果的次数。比如下面的例子里,捕捉到一次调用就退出命令: ...

March 22, 2026 · 2 min · santu

让你实现一个短链服务,你会考虑哪些问题_

典型回答 短链服务大家都知道是啥,就是把一个长链接转成短链接,然后用户可以通过短链接访问到长链接对应的网站,那么,想要实现一个短链接服务,需要重点考虑以下几个问题: 1、短链生成算法 2、长链和短链的存储 3、如何实现短链跳转到长链 4、如何确保短链跳转的高性能 5、针对热门短链如何提升性能 6、如何防止短链被滥用 7、是否需要统计访问数据 8、同一个长链生成的短链是否要一致 9、短链是否需要有过期机制 问题定义好之后,就可以逐一的想办法解决了。 短链生成算法 要生成一个不重复的短链接,在行业内有一种比较常见的做法,那就是基于****MurmurHash+62进制转换的方式来实现。这个方案同时兼顾快+冲突少+短等优点。 具体的什么是Murmurhash,原理是啥,以及为啥要用62进制,可以看下面这篇文章,介绍的很全面了。 ✅如果让你实现短链服务,如何生成不重复的短链地址? 长链和短链的存储 短链和长链,是一对。当我们把https://www.hollischuang.com/archives/6998通过短链服务转成https://y-tb.v3z.cn/QZFGr4这样的链接之后,我们会把短链接分享出去。 那么用户通过短链接来访问的时候,就需要知道这个短链对应的长链接是什么,就需要有个地方来存储他们之间的关系。 其实,这个比较容易想到是比较适合用Redis这种K-V存储来保存的,我们只需要把短链接作为key,长链接作为value,在用户用短链接访问的时候,通过key去查询出长链接就行了。 但是这里需要注意的是,Redis的存储不能保证100%可靠,所以还是需要定时的同步到MySQL中,确保数据不丢。 如何实现跳转 当我们从Redis中找到了短链接对应的长链接之后,是需要让浏览器做跳转的,那么,想要跳转就涉及到2种了,分别是301和302。 HTTP 301/302: 301 永久重定向:资源已经永久移动到新的 URL,浏览器和搜索引擎会缓存这个重定向结果。 302 临时重定向:资源临时移动到新的 URL,客户端不应该缓存,后续请求仍然访问旧 URL。 所以,其实301和302最大的区别主要是302因为不会被浏览器缓存,所以会每次访问短链都会经过服务端重新查一下新的长链接。而301则不需要,会被浏览器缓存,下次再访问就直接跳转了。 适合用哪个?我认为302比较合适一点,以下几个原因: 1、因为302这种我们可以做数据统计,可以统计出一个短链的实时记录访问数据(PV/UV、来源、设备等)。而301则无法被统计。 ...

March 22, 2026 · 1 min · santu

让你设计一个秒杀系统,你会考虑哪些问题?

典型回答 这个问题是一个比较考验知识储备、灵活运用能力、以及问题解决能力的问题。 想要回答好这个问题,首先需要学会做问题的拆解,所谓的定义问题。作为一个5年工作经验以上的人,尤其是偏架构方向的技术人员,要先学会如何定义问题,然后才是解决问题。如果定义问题都做不好,所谓的解决的也都是伪问题。没意义了 秒杀系统,我们需要先拆解一下他会遇到哪些问题,面试官在问这个问题的时候,其实在问的到底是什么问题? 一个秒杀系统,有以下几个特点,需要解决这么几个问题: 1、高并发瞬时流量 2、热点数据 3、数据量大 4、库存的正确扣减 5、黄牛抢购 6、重复下单 7、对普通交易的影响 那么,在定义出这个7个问题以后,就可以逐一的想办法解决了。 高并发瞬时流量 秒杀是一个典型的具有高并发瞬时流量的场景,在这种情况下,如何提升系统的稳定性、可用性就至关重要了。 一般来说,我们在整体架构设计上面,会做逐层的流量过滤 ,一次用户的秒杀请求会经过客户端、CDN、Nginx、Web应用、缓存、数据库等等。 首先是秒杀功能的开启,以及前端资源的访问,这部分内容一般都是提前放到CDN中,让这些静态资源离用户更近就能让用户访问的更快。 接下来,能做的就是尽量在离用户更近的地方做流量的过滤,比如很多秒杀系统,会在前端,也就是客户端层面做一些请求的随机丢弃,这些被丢弃的请求就直接返回失败,或者系统繁忙,让用户重试。过滤掉一部分流量向服务端发送。 在服务端接受请求之前,还会先经过Nginx做统一接入,Nginx不仅可以用来做负载均衡和流量的分发,其实他也是可以做流量的过滤的,这里面可以配置一些黑白名单、可以通过IP进行限流、也可以做一些业务校验都是可以的。 再之后就是到服务器上面了,服务器层面也是配置很多限流策略的,基于sentinel,或者自己实现一些限流算法,都是可以做动态限流的、 还有就是,服务器中有一些查询操作,和一部分写操作,其实是可以用缓存来抗一下的。在缓存上,本地缓存要比分布式缓存的性能更高,近端缓存要好于远端缓存。 因为秒杀业务上本来就是支持失败的,所以,我么就可以通过层层过滤,让更多的流量在前面被过滤掉。 ✅如何设计一个能够支持高并发的系统? ✅服务端接口性能优化有哪些方案? 热点数据 秒杀系统另外一个比较典型的特点就是会存在热点数据,因为大家都会抢购同一件商品,那么这个商品就会变成热点数据。 对于热点数据,不仅有高频的查询请求,还会有非常高频的写请求,比如库存的扣减等等。 关于库存扣将我们后面单独说,这里先说查询请求和其他的写请求。 对于这种热点数据,解决方案主要就是拆分+缓存。 首先说拆分,就是把一个热点的数据,拆分开,拆成没那么热的多个数据,在通过负载均衡让不同的请求分散到不同的数据上。 还有就是用缓存,一般来说,秒杀是可以提前预知哪些数据会变成热点的,所以可以提前做一些缓存的预热,对于热点数据,不仅需要在Redis中做预热,还需要在本地缓存也做预热,避免Redis的热key问题。 ✅什么是预热?它有何作用? ✅什么是热Key问题,如何解决热key问题 数据量大 秒杀系统会有高频的下单,那么就会导致最终数据量也会很多,那么最终产生的订单量可能就会很大。 数据量一大,就会带来查询效率低的问题。 这时候就可以考虑要么就加缓存、要么就用ES、要么就做分库分表。还有就是做数据归档,把历史数据归档掉,无非就是这么几个方案了。 ✅什么是分库?分表?分库分表? 库存的正确扣减 秒杀因为是一个高频的并发库存扣减的场景,所以,如何提升库存扣减的性能,并且保证他的准确性,这是一个在秒杀业务中极其重要的课题,稍有不慎就会带来超卖、少卖等问题。 这几个我们单独讲: ✅库存扣减如何避免超卖和少卖? 黄牛抢购 秒杀商品一般都有差价可以赚,所以会有很多黄牛来一起抢购,那么如何识别这些黄牛,并阻挡他们的下单,也是比较关键的。 首先防刷最重要的就是检测哪些用户可能是黄牛,这其实就涉及到风控的领域了,一般都是需要借助算法模型,根据用户的IP、设备信息、网络信息、行为数据等进行分析。 那么算法推算出哪些可能是黄牛用户以后,就需要我们针对他们的流量进行防控。 这部分用户的ID直接可以加入黑名单中,然后黑名单可以在Nginx中,以及业务系统中都可以做过滤,如果发现用户在黑名单中,就直接拒绝请求。 除了用户ID以外,还需要对他的IP地址、设备等进行限流,比如限制某个IP一段时间内只能下单几次,基于令牌桶、漏桶等限流算法都能实现。 我们也可以直接借助nginx、sentinel、guava等进行限流的实现。 ✅什么是限流?常见的限流算法有哪些? 还有一种防刷,是防止别人直接通过脚本的方式直接调我们的接口,这种的话,我们可以借助token的方式实现防刷。 当用户访问页面时,发放一个token,请求过来时,需要把token带过来,这时候我们做校验,token合法则接受请求,并且让token失效。token不合法或者已失效则直接拒绝请求即可。 重复下单 在秒杀业务中,很多用户会频繁尝试下单支付,那么就可能造成重复下单。那么就会额外占用我们的库存,最终导致少卖。 那么,我们就需要通过一些手段来避免重复下单。 首先,基于我们前面提到的token,是可以做重复下单的检测的,也就是说如果用户在一个页面上,没刷新页面的话,token是一样的,那么我们基于token做重复下单检测就行了。这样可以避免重复下单。 另外,秒杀业务中,其实通常都是限购的,所以我们可以结合业务场景,判断用户是否已有在途订单,通过限购方式避免重复下单。 那么,在以上过程中,都可能出现token检测、以及订单重复性判断时因为并发导致重复的,那么就需要引入锁机制来保证下单操作的幂等。 ✅如何解决接口幂等的问题? 对普通交易的影响 秒杀一般是电商网站中的一个功能,大多数情况下是和其他的业务在一起部署的,不仅是服务器,还包括缓存、数据库这些。 那么我们就需要想办法来避免秒杀业务对于普通交易的影响过大的问题。 这时候就需要考虑到隔离了。隔离有两种,逻辑隔离和物理隔离。想要彻底的话,那肯定是物理隔离。 所谓物理隔离,就是前后端服务、包括数据存储都彻底分开。 或者应用上面做物理隔离,数据上做逻辑隔离。数据的逻辑隔离就是在订单、商品上面打标,标记出来是秒杀订单,方便后续查询及数据分析等。 扩展知识 业务手段 有的时候,我们不能只想着用技术手段解决所有问题,其实,如果在业务上能做点事情的话,如果这些做法并不影响用户体验,那么就可能让技术实现上大大简化方案,整个系统的成本和稳定性也会有大大的提高。 ...

March 22, 2026 · 1 min · santu

让你设计一个订单号生成服务,该怎么做_

典型回答 在设计一个订单号生成服务的时候,我们需要解决以下几个问题,当我们知道这些问题该如何解决的时候,整体的方案也就大概知道了。 唯一性:订单号必须保证唯一性,否则会出现订单冲突和数据不一致等问题。可以使用一些常见的唯一性生成算法,例如UUID、Snowflake等。 ✅什么是UUID,能保证唯一吗? ✅什么是雪花算法,怎么保证不重复的? 数据量:在设计订单号的时候,需要充分的考虑到后续数据量变大的情况下该如何兼容。所以需要提前预留出足够的位数。 可读性:订单号应该易于理解和记忆,可以根据业务需求自定义订单号的格式和组成方式,例如使用时间戳、随机数、用户ID等信息来构造订单号。 基因法:订单系统到最后都可能会考虑分库分表,所以在最初设计订单号的时候,需要考虑将和分表有关的字段编码到订单号中,如买家ID等。 ✅分表字段如何选择? 可扩展性:订单号生成服务需要支持高并发、分布式部署和横向扩展等特性,可以采用分布式ID生成器、Redis等技术来实现。 高性能:订单号生成服务需要具有高性能和低延迟的特点,可以使用内存缓存、异步处理等技术来优化性能。 高可用性:订单号生成服务需要保证高可用性,可以使用多节点部署、负载均衡、健康检查等技术来提高系统的可靠性和稳定性。 比如我自己出的的高并发实战项目中,订单号是这样组成的: 通过2位数字表示业务类型,如交易订单、支付单、结算单等都是不同的业务类型,可以有不同的编号。 中间的18-20位用一个唯一的ID来表示,可以用雪花算法,也可以用Leaf,总之就是他需要保证唯一性。 最后4位,基于基因法,将分表后的结果获取到,把他也编码到订单号中。

March 22, 2026 · 1 min · santu

说一说多级缓存是如何应用的?

典型回答 提到多级缓存,很多人第一时间想到通过Guava本地缓存+Redis分布式缓存组成的二级缓存。其实,多级缓存可并不只是这两层,在一些场景中,可能有很多层。 我们在下面这篇中介绍过,近端缓存的概念,我们提到"近端缓存(Edge Cache)通常是指位于网络边缘、离用户更近的位置的缓存。“那么,我们以一个电商的秒杀场景举例,说一说一个多级缓存的真实应用场景。 ✅本地缓存和分布式缓存有什么区别? 一个真实的秒杀业务,从前到后,可能涉及到5层缓存。 客户端缓存 首先,离用户最近的一定是客户端了,比如我们使用的手机、电脑等,在手机上的APP以及电脑的浏览器 ,都是可以支持做缓存的。 秒杀场景中,比较典型的客户端缓存的例子就是秒杀页面的倒计时。 当用户第一次访问秒杀页面的时候,会向后端服务器请求查询秒杀开始时间,然后这个时间就可以在客户端进行缓存下来了。 然后客户端自己就可以基于这个开始时间做倒计时了。 设想一下,如果每一秒钟都要向后端发送请求,那么请求量实在是太大了,所以通过客户端缓存的方式,可以大大降低后端服务器的压力。 CDN缓存 秒杀活动中,有很多商品相关的信息,比如商品的图片信息,这些前端的静态资源其实变化的频率并不是很高,那么其实就可以通过CDN来做缓存。 这些静态资源,如图像、HTML、JavaScript 和视频。提前放到CDN中,让用户可以就近的访问到CDN快速的拿到静态资源。 关于什么是CDN,以及为什么他能作缓存,为啥他比应用服务器更快,我们在下面的题目中单独展开: 什么是CDN,为什么他可以做缓存? Nginx缓存 在客户端及CDN缓存的基本都一些静态的数据,但是还是有很多数据不适合做缓存的,那么这时候用户请求过来的话就要发送到后端服务器中。 而在请求给到服务器之前,会先经过Nginx做负载均衡。 为了提升请求的效率,降低对后端服务器的压力,很多时候我们也会在Nginx中做一些缓存。 比如一些静态资源,除了放到CDN中,Nginx中也可以缓存一份,当没有CDN的时候,可以优先到Nginx中获取。 在秒杀场景中,我们需要做一些用户的鉴权,比如用户是否登录,是否是黄牛用户,用户的IP是否被封禁。这些信息一般是放在Nginx中的,可以在这里面做一些前置的校验,把一些非法的请求就直接都给拒绝掉了。 Web App 缓存 当Nginx请求过滤之后,有些合理请求就需要发送到Web App当中,这时候就会涉及到很多具体的业务逻辑了。 但是,应用当中也会经常使用本地缓存来提升查询效率。但是本地缓存我们都知道,可能存在不一致的问题,所以这里面存储的信息一般是变化没那么频繁的。 比如一次秒杀活动的开始和结束时间、比如秒杀的用户的用户等级这些信息,基本都是变化没那么频繁的,就可以放到本地缓存中,可以快速的读取。 分布式缓存 有些数据因为要考虑他的一致性,所以没办法放到本地缓存中,比如商品库存,这种就需要放到分布式缓存中,比如Redis。 在秒杀场景中,我们经常使用Redis来做库存扣减,其实这就是利用了Redis来做缓存的一种具体案例。 以上,从前到后串联了下秒杀业务的多级缓存的实现,关于秒杀的其他问题,大家可以参考: ✅让你设计一个秒杀系统,你会考虑哪些问题?

March 22, 2026 · 1 min · santu

如果你的业务量突然提升100倍QPS你会怎么做?

典型回答 首先看下这个业务量的提升的原因和特点是什么? 那么就有很多种情况了: 正常情况:比如就是业务有好转有起色了;或者刚好蹭到了某个热点 异常情况:被DDOS了 如果是被DDOS攻击了,也会导致流量提升,那么这种就通过防止DDOS攻击的手段来解决。 比如上一些降级、限流、熔断等等的机制,来保护你自己的系统以及下游系统不会攻击而被打垮。 ✅什么是DDoS攻击?如何防止被攻击? 那另外正常情况的话,如果是蹭到了某个热点,那么就可以通过临时方案来解决,不需要考虑的太长久,那么最简单的就是扩容,增加集群的服务器数量,提升机器的硬件资源配置,让整体的吞吐量提升。 那么,如果是业务自然增长,就需要考虑长期方案了,想让系统真的可以提升并发性,提升到可以抗100倍QPS,那么可以做的事情其实就是另外一个问题:如何设计一个高并发系统 ✅如何设计一个能够支持高并发的系统?

March 22, 2026 · 1 min · santu

每天100w次登录请求,4C8G机器如何做JVM调优?

典型回答 首先,我们需要问清楚,一天100W次的登录,在一天内有没有某个时段是高峰的?高峰期的QPS大概可以达到多少。 如果没有高峰期,虽然100万听上去挺多的,但是其实平均下来一秒钟的QPS也就10,这个量的话,其实根本不需要做什么特别的JVM优化。 一般业务场景中,都是有自己的业务高峰期的,比如电商业务基本上上午十点和下午两点是业务高峰期,基本上这时候的QPS是平时的20倍都不止。 我们假设登录业务存在高峰期,峰值时长大概持续1个小时,峰值的QPS可以达到200。那么需要做哪些优化? 作为一个登录服务,一般来说我们在接收到请求之后,只需要给用户进行鉴权并把结果返回给前端就行了。在这个过程中一般不太会去查询太多的数据,比如权限什么的也都是在后面访问页面再查询的。所以,峰值200左右的QPS,对于JVM的内存来说,最主要的就是会因为远程调用,而创建出很多请求参数和请求的响应。而这些对象基本都是朝生暮死的,接口调用结束之后就会被回收掉。并且通常来说这些对象也不会很大, 因为登录并不是注册,其实并不携带特别多的信息,那么也就是说,会产生大量的小对象,即新生代会不断的创建对象,并被回收掉。 基于以上简单分析,我们看下该如何做我们的JVM调优。 堆内存设置 首先是堆内存大小的设置。当我们的机器只有4核8G的时候,堆内存的大小肯定不能太大,一般不建议设置的太大,因为我们需要给机器上的其他应用预留出一部分内容。所以,**我们一般建议都是把JVM的堆内存设置成操作系统内存的一半,也就是4G。**至于初始内存和最大内存,我们这场景中建议设置成一样的。这样可以避免 JVM 在运行过程中频繁进行内存扩容和收缩操作,提高应用程序的性能和稳定性。即: 1 -Xms4G -Xmx4G 垃圾收集器选择 在设置了堆空间的总大小之后,我们需要考虑用哪种垃圾收集器。另外,我们前面分析过,这个业务中会频繁在新生代创建并销毁对象,那么,就意味着新生代的GC会比较频繁。所以我们需要选择一种在GC过程中STW时间短的,并且在年轻代的回收中也能发挥效果的。 在新生代的垃圾收集器中,主要以Serial、ParNew、Parallel Scavenge以及支持整堆回收的G1了。 因为新生代采用的都是复制算法,所以不太需要考虑碎片的问题,我们主要考虑吞吐量和STW的时长就行了。 首先排除单线程的Serial,剩下ParNew是一个并发的收集器,Parallel Scavenge更加关注吞吐量,而G1作为JDK 9中默认垃圾收集器,他不仅同时具有低暂停时间和高吞吐量的优点,但是他对内存有要求,最小要4G, 从使用门槛上来说,G1是可以用的,因为一般来说,内存要大于等于4G的话,才适合使用G1进行GC。 所以,我们采用G1作为垃圾收集器: 1 -XX:+UseG1GC 在使用了G1之后,其实他自己是有一套自动的预测和调优机制的。我们只需要通过-XX:MaxGCPauseMillis参数来设置最大停顿时间就行了。一般建议设置到100-200之间,一般这个时长对用户来说基本无感知: 1 -XX:MaxGCPauseMillis=200 其次,我们还可以自己调节一些G1的配置,比如设置他的GC线程数,可以先配置4个线程数进行GC,后续根据实际情况再做调整: 1 2 -XX:ParallelGCThreads=4 // 设置并行 GC 线程数为 4 -XX:ConcGCThreads=2 // 设置并发 GC 线程数为 2 各区大小设置 G1的内存划分是自适应的,它会根据堆的大小和使用情况来动态调整各个区域的大小和比例。但是,我们也可以通过一些JVM参数来手动设置G1的各个分代内存配置。 G1 中的分代和其他垃圾回收器不太一样,它不是严格按照年轻代和老年代划分的,而是通过划分各个区域的存活对象数量来实现垃圾回收的。因此,G1 中不需要像其他垃圾回收器那样设置新生代和老年代的大小比例,而是需要设置一些区域的内存配置。 -XX:G1NewSizePercent 和 -XX:G1MaxNewSizePercent分别用于设置年轻代的初始大小和最大大小,它们的默认值分别为 5% 和 60%。针对我们的业务场景,我们其实可以适当的调高一下年轻代的初始大小,5%的比例太小了,我们可以调整到30%。 1 2 3 4 5 -XX:G1HeapRegionSize=2m:将 G1 的区域大小设置为 2MB,以提高垃圾回收的效率和精度。 -XX:G1NewSizePercent=20:设置年轻代的初始大小为堆的 20%。 -XX:G1MaxNewSizePercent=50:设置年轻代的最大大小为堆的 50%。 -XX:G1OldCSetRegionThresholdPercent=10:设置老年代的大小为堆的 10%。 -XX:G1HeapWastePercent=5:设置垃圾回收后留下的未使用区域的最大比例为 5%。 添加必要的日志 因为以上配置都是根据业务大致分析出来的初始配置,所以我们一定是需要不断地调优的,那么必要的日志相关参数就要添加。如: ...

March 22, 2026 · 1 min · santu

留言给博主