• 问: 就是我现在有一个字串,希望找出它的最长不重复子串的长度。我举个例子啊,就是这个组串,其中最长的不成的组串长度是三。那比如说abcabca啊,这些都是它的最长不重复的串啊,abca呢这就不是因为这个a 有两个重复了。好吧,再把写到这个版本上。

嗯,好的。嗯,我写完了。

  • 问: 嗯,行,那简单说一下你这道题的思路吧。

嗯,可以的,我用了一个滑动窗口,滑动窗口每次都会向右扩张一个字符。然后检查这个字符在滑动窗口里面呢,是不是存在的。要是不存在的话呢,就会继续向右扩张。要是存在的话,窗口左侧呢就会开始收缩。直到收缩到这个新字符没有重复。然后这个i 我是用来记录窗口的最大长度啊,这个长度呢就是要求的最长不重复子串的长度。

  • 问: 你在项目里面用redis 是做缓存呢,还是做存储呢?

嗯,主要是用来做缓存的,

  • 问: 主要是用来缓存什么数据呢?

这个是用来缓存一些商品信息。

  • 问: 在你这个小店第一次上线的时候,Redis有没有进行过预热呢?

进行了一个简单的预热。我们在上线之前预测了一些热点的商品,然后就把这些商品信息添加到了redis 里面。然后随着用户不断请求我们的服务,会把这个数据从mysql 加载到redis 里面。

  • 问: 我举个例子啊,在一个新商品刚刚上线的时候啊,我们只更新到mysql 里。redis 里面没有这个商品的信息。那这个时候如果有大量的用户并发来访问这个新商品,会给mysql 带来很大的压力啊,我怎么来解决这个问题呢?

嗯,这个时候呢可以考虑给mysql 加几个从库来分摊这个压力。或者在新品上线的时候呢,同时更新到redis 和mysql 里面。

  • 问: 那mysql 要增加多少个从库呢?

那这个呢就可以根据实际请求数来算一下。我们预估新品上线是之前流量的两倍的话啊,我们可以考虑加一倍的从库。

  • 问: 那这些从库在扛完这个流量之后呢,是不是可以下线了呢?这会不会有点资源浪费呢?这些机器我们也不能退给他们服务器的人,对吧?我换个问法,我们Redis做缓存的时候,总会有一些比较热门的商品在缓存里边。但是那些冷门的商品就不在缓存里边了,对吧?那突然有一天一个商品成了爆款啊,访问量一下上去了。那这个时候Redis里边没有这些商品啊,这些请求是不是都打到mysql 上面了,这个时候加从库肯定来不及嘛。那我们怎么解决这个问题?

那可以加个分布式锁,比如说呃使用Redis setnx命令来实现一个分布式锁。key就是商品的id 然后value 就是持有所的线程id,在并发请求mysql 之前呢,需要服务线程先去获取这个分布式锁。然后只有成功获取锁的线程,才会去请求mysql,然后其他没有获取到锁的这个线程呢就会在这儿阻塞等待。然后获取到锁的那个线程,就会把数据从mysql 加载到redis 中。其他没有获取到锁的这个线程呢再从阻塞状态恢复之后啊,就可以再去查询一遍redis。这样的话呢就可以从缓存当中拿到这个商品的数据了。这样的话就可以保证只有一个线程去加载mysql里面的数据。那这样mysql 的压力就会小很多了。

  • 问: 有这么一个问题啊,我们发现一个恶意的用户,不停的在访问我们一个没有的商品啊,这个时候缓存就击穿了嘛,这个请求全部打到了我们mysql 上面去啊,我们应该如何保护我们的mysql 呢?

嗯,那我们可以在redis 里面设置一个value 为空的key值。你比如说key 值是这个商品的id,value 的话呢就是空字符串。因为我们确实没有这个商品的数据嘛,所以说我们给用户返回一个空字符串也是合理的。这样的话在用户访问这个没有的商品的时候呢,就会请求到Redis上面。然后这部分流量呢就不会打到mysql。

  • 问: 那如果后面我们这个没有的商品上架了啊,这个商品信息呃需要更新到我们的mysql 里面去。那更新完之后呢,我们的Redis里边还是一个空值。我们怎么保证这两块数据是一致的。

嗯,那我们可以在这个商品信息添加到这个数据库之后呢。把redis里面这个空空值删掉就行。这样的话后面有用户请求这个新商品的时候呢,就会把mysql 里面的数据加载到redis 里面了。因为我们前面做过那个分布式锁的设计嘛,所以说只会有一个线程来加载这个数据。这样的话呢就可以让mysql 的数据同步到redis 里面了。

  • 问: 嗯,这有个问题啊,如果我们写入mysql 的时候成功了啊,然后去删redis 里边儿这个空值的时候失败了。那这个商品的信息在redis 里边儿就永远更新不到了。那始终是个空值啊,那怎么办呢?

对的对的,可能会有这个问题。那我们先去删除redis 里面这个空值。然后我们再去写入数据库。那这样的话呢就可以避免这个问题了。这样的话当用户来查的时候呢,redis 里面是查不到的,就会去mysql ,去那里面加载最新的数据。这样的话上面的信息就能够正常的加载到redis 里面了。

  • 问: 那我们再来考虑一个并发的问题啊,就是当我们在清空缓存中的空值的时候,写入Mysql之前啊,恰好有一个用户后来查了一下这个商品。它是不是还是会将空值它加载到redis 呢?这还是会出现不一致的问题啊。

那感觉先操作谁都不太对啊。呃我考虑一下。我可能我可能考虑把mysql 和redis 的修改做成一个分布式事务。但是但是感觉引入分布式事务会比较复杂。嗯,那我可以在写入这个mysql 之后,立刻发送一条Kafka消息。这条Kafka消息发送成功了,才能算真正的更新成功。然后consumer 消费到这个消息之后呢,就会去更新缓存。如果更新redis 失败了,那我们就不提交offset,最后总会有一个consumer 能更新成功吧。那这样的话呢就可以让缓存和mysql 的数据最终保持一致性。

  • 问: 嗯,这个思路也可以,你发送Kafka消息其实是为了让更新Redis的服务知道mysql 更新的结果,对吧?除了这种方式,还有没有其他的方式可以感知到mysql 的数据更新呢?

嗯,我想想哈。可以监听bin log,可以让更新Redis的服务来监听mysql 的bin log。这样在他感知到写入mysql 的结果之后呢,再更新。这样其实也行啊。

  • 问: 呃,你在项目中用Kafka是用来实现什么样的功能呢?

我这边主要是用来消峰的。当时我的上游服务是监控的Api网关层,他负责接到业务层发过来的监控数据,然后对监控数据进行暂存,还有拆分,就会造成请求量放大,还会造成我这边收到脉冲状的请求。要是直接使用RPC请求,我们这边的话呢就可能会造成我这边的服GC,所以就用Kafka进行消峰。

  • 问: 呃,描述一下Kafka消息从producer 发到broker 啊,然后再被consumer 消费的整个过程吧。可以尽可能的说一些你了解的细节。

嗯,在发送消息的时候,这个消息会写到producer侧的缓存里面去,并不是直接就发送出去的。缓存里面会攒一批数据,然后再发出去。然后在发送的时候producer会根据partition的路由规则选择一个partition,然后发到这个partition 所在的机器上。然后在这个broker 收到了这个producer 发来的消息之后呢,就会写到本地的磁盘里面去。Kafka当中呢就有一个副本的概念,里面分成leader 和follower。只有leader 节点能够写数据啊,然后follow 节点的话呢会跟leader 节点同步。follower 呢它只是作为一个备份。然后消息写完之后呢,consumer 那边就可以进行读了。读的话呢就是一个拉的模式。consumer 这侧呢有一个consumer group 的概念。一个partition可以被多个consumer group 消费。那在一个consumer group 里面呢,可以有多个consumer。然后这些consumer 呢会被分配消费不同的partition。然后在消费的时候,consumer 会根据一个offset 的偏移量去拉消息。然后消费完之后,consumer 会提交这个offset 的值啊,表示这个消息已经消费完了。

  • 问: 来看个来看个实际问题啊,我现在有一堆单词,这些单词呢我希望相同的单词对同一个consumer 进行消费啊,我应该怎么处理呢?

那我们可以在发送的时候,把这些单词的哈希值作为partition key,那这样的话呢在producer 发送数据的时候,就会按照这个partition key 把相同的单词写入到同一个partition 里面。然后这个partition 的话,只能被一个consumer 消费嘛,所以就可以实现这个需求了。

  • 问: 那Kafka是如何做到消息是有序的呢?

Kafka在一个topic 里面不能保证消费消息是有序的,他只能保证一个partition 里面的消息是有序的。所以说如果我们需要发送端和消费端的消息顺序是一致的话,我们需要把这些消息写入到同一个partition 里面。

  • 问: Kafka如何保证你发出去的消息是不丢的呢?

嗯,Kafka不丢消息,这点呢要从几个维度上去说。一个是producer在发送消息的时候,broker 会返回一个Ack的响应啊,如果收到Ack响应,producer 就会认为这个消息已经发成功了。那如果没有收到响应的话呢,producer 就会再重试,缓冲区里面的这些消息呢就会再发送一次啊,这个就是消息发送时候的可靠性保证了。然后再就是broker 接收到的消息之后,他除了在leader 这里面进行持久化,还会有多个节点去同步这一条消息啊。然后在指定数量的follower 节点同步完成之后才会给producer 返回Ack响应啊。这样的话即使leader 宕机了,或者说磁盘损坏了啊,那个follow 节点呢也可以选出一个新的leader 节点,继续提供这个服务。这样的话,这些已经提交的消息就不会再丢失了。然后再就是consumer 这侧的话,通可以通过offset 来控制消费的位置。就是如果我没有消费成功这条消息,我可以不提交offset值,这样的话就可以保证这个消息至少是被消费一次。

  • 问: 嗯,你说Kafka,吞吐量比较高,他这个高吞吐量是怎么实现的?

Kafka提高性能的手段有很多。我们先说producer侧,在producer 发消息的时候会先进行缓冲,然后批量发送,然后broker在接收消息之后进行写入磁盘的时候,是顺序写文件的,然后也是批量进行刷磁盘操作。然后再说消费侧consumer 在消费消息的时候是批量去拉起消息的。然后broker 在接收到查询请求的时候呢,就会使用一个稀疏索引来快速定位offset在那个日志的位置。然后使用这个零拷贝的方式进行读取。无论是producer broker 还是consumer,都是使用了Nio的方式来进行网络操作的。所以说能够承担非常大的连接数,差不多就是这样了。

  • 问: 我现在想通过Kafka发一条延时消息啊,延时消息大概的意思就是我现在通过producer 把消息发出去了,但是我不希望consumer 那么快就消费掉,而是在未来的某一个时间点。比如说一分钟五分钟之后才消费到这条消息。这个Kafka有什么解决方案吗?

Kafka本身是不支持延迟消息的。那我们可以在消息的key 里面加一个延迟消息的标识,还有这个过期时间啊。如果包含这个标识啊,这个消息就是延迟消息啊。那这样的话我们就再写一个consumer group 来消费这个topic。消费到这个topic 的消息里面之后,我们再看一下这个消息的key ,如果是延迟消息的话,我们就把这个这些消息暂时存库。然后呃我们去起一个定时任务,去扫这个mysql 啊。如果发现延迟消息到期了呃,就把他的这个key 改成非延迟消息啊,然后重新发到这个topic 里面去,就可以了。

  • 问: 如果延迟销量比较大啊,定时任务扫不过来了。比如说我现在mysql 里边已经堆积了一百万条延迟消息,然后还有源源不断的延迟消息啊,写到mysql 里面去。但是我的定时任务呢只能扫一万条消息出来,这样的话应该怎么办呢?

那感觉就变成了mysql 优化的问题了。那这样的话我可能考虑加索引,然后分库分表,然后起多个定时任务,这样分别去扫这种这些不同的分表,还有分库。

  • 问: 还有一个问题,如果说我的原始消息跨度可能比较长。比如说有的消息是一分钟之后出发,有的是有的消息是三十秒之后出发的。那这样的话简单的分库分表回答什么问题呢?

嗯,那这样的话分库分表可能会把一堆长时间之后触发的延迟消息分到一个表里面去。

  • 问: 啊,然后每次扫这个表的时候都会扫不到数据啊,因为近期没有延迟消息触发嘛,所以这也是个问题哦。

那这样的话我可以在每一次扫表的时候,记一下这个表里面最近的一次触发时间。然后等到那个时候再去扫。

  • 问: 嗯,这个方案还是有点小问题啊,后面可以再思考一下。嗯,你有没有考虑过结合时间轮来处理这种延迟消息比较多的场景呢?

结合时间轮的话,那我们可以使用时间轮加存储的方案。时间轮的话呢就是一个环形队列,每一个元素表示一个时间单位。然后每一个格子里面对应一个表,然后这个表呢就是这个时间点要触发的延迟消息。这样的话当每一个时间单位到期了之后。对应表中的这些消息就会写回到这个topic 里面去。那要是时间跨度比较大的话,我们可以考虑多级时间轮加存储的方式。如果是多级时间轮的话,就是越往上层的时间轮时间单位就越不精确啊。就比如说最上层时间轮的一个时间单位是一个小时,然后再下一层的话可能是一分钟啊,然后再往下的话可能是一秒啊,然后秒级的这个时间轮中才会指向真正存消息的表啊,大概是这样。

  • 问: 嗯,你有没有使用过Kafka里面的事务消息?

不是很了解,只是知道它是用来实现消息只被消费一次的这个语义的。在实践里面呢也没有用过。我一般都是在consumer 侧做幂等性处理的。