做了 3. Longest Substring Without Repeating Characters,求字符串中最大不重复子串的长度。
最开始的思路是:
- 遍历字符串,将遍历到的元素加到一个 list 中
- 如果当前遍历到的元素已经在 list 中了,那么说明重复,则最新的子串应该是 list.sublist(indexOf(element) + 1, list.size())。最终结果为最长子串的长度。
但是提交的时候始终超时,ist.sublist(indexOf(element) + 1, list.size()) 的操作太耗时间了。
第二种是看了部分讲解后才明白的:
- 记录当前子串的起始位置,初始值为 0
- 遍历字符串,将遍历到的字符和其索引存到一个 map 中
- 如果遍历到的字符已经在 map 中了,如果当前索引 大于 start,则 start 变为 start + 1,最新子串则为 start 到当前索引
- 比较最新子串长度与已有最大子串长度,取最大值。
这样可以不占用额外的空间,以「滑动窗口」的形式解决。
下面分别是 Java 和 Go 的代码实现:
public class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int start = 0;
int len = s.length();
int maxLength = 0;
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (map.containsKey(c)) {
int lastIndex = map.get(c);
// 这里需要判断,如果 lastIndex 小于 start 则不能更新 start
// 比如 abcbac,遍历到第二个 a 时 start 应该为 2,而 lastindex 此时为 0。
if (lastIndex >= start) {
start = lastIndex + 1;
}
}
int currentLength = i - start + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
}
map.put(c, i);
}
return maxLength;
}
}
func lengthOfLongestSubstring(s string) int {
m := make(map[rune]int)
maxLength := 0
start := 0
for index, value := range s {
if value, ok :=m[value]; ok && value >= start {
start = value + 1
}
currentLength := index - start + 1
if currentLength > maxLength {
maxLength = currentLength
}
m[value] = index
}
return maxLength
}
最终时间复杂度为 O(N),空间复杂度为 字符串占用空间 O(N),Map 所占空间取决于不重复元素个数,因此算法的空间复杂度也为 O(N)。
读耗叔练级攻略推荐的微软云设计模式可用性第三篇:Throttling pattern。
限流模式指的是,通过控制某个应用实例、个人租户或者整个服务的资源占用情况,保证系统在负载过大甚至是极限情况下依然可以提供服务以满足 SLA 的要求。
对于部署在云上的应用,其系统负载会随着活跃用户的数量或者其所执行的任务类型的变化而变化。比如在营业时间可能会有更多的用户活跃,系统可能会在月末进行需要占用大量资源的计算任务,甚至包括突发的意外事件,这些都会导致系统的负载升高。如果系统对资源的要求超过了可用资源容量,那么就会导致性能下降甚至崩溃。如果系统需要满足一定的 SLA,这种情况是不被允许的。
对于负载变化可能有不同的处理方式。一种常见的方式就是自动扩容以满足应用对资源的需求。但是自动扩容可能会引发一系列的资源配置,而且这种配置可能不是立即完成的,中间扩容过程中的时间窗口依然会出现服务性能下降甚至不可用的情况,另外当瓶颈在数据库时,我们自动扩容服务是没用的,因此还需要其他的解决方式。
实现自动扩展的另一种策略是允许应用程序仅能使用一定限额的资源,当到达限额时采取限流措施。此时系统需要监控应用对资源的占用情况,然后达到限制时对一个或者多个用户的请求做限流处理。这样可以在不占用额外资源的情况下保证系统的 SLA。关于资源使用信息监控,可以参阅这篇文章 Instrumentation and Telemetry Guidance。
系统采取的限流策略可以有如下几种:
-
对用户请求添加限制,如果某个用户在过去一定时间内的访问次数超过 N 次,则拒绝来自该用户的请求。为了达到该目的系统需要监控每个用户或者每个租户的资源占用、请求访问情况。具体策略参考 Service Metering Guidance(服务计量指导)
-
禁用或者降级某些非核心功能, 保证核心业务功能有足够的资源不受影响。比如如果应用是视频流传输服务,那么可能通过降低其分辨率来减少资源的占用。
-
添加负载均衡,使得系统活动保持在一个稳定的状态,该方式更具体的实现可以参考上一篇 Queue-Based Load Leveling pattern。对于多租户的环境,如果不同租户需要满足不同的 SLA,那么高优先级租户的事件应该优先处理,完成后在处理低优先级租户的时间。对于该方式可以参考 Priority Queue pattern(优先级队列)。
-
推迟执行 某些低优先级的应用或者租户要执行的任务。可以将这些任务暂停或者添加执行限制,此时可以抛出异常,告知应用或者租户当前系统繁忙,任务将会稍后重试执行。
以下是设计限流模式时需要注意的几点:
- 限流设计一般是系统整体架构的一部分,因此一般在系统设计早期就要考虑如何进行限流,否则系统设计开发完成后在添加限流是一件比较麻烦的事情。
- 限流操作的执行速度一定要快。当系统负载增加时要快速做出响应,并且当负载回归正常水平时也要保证系统功能也要回归正常,不能出现负载恢复正常了但是依然有服务或者用户被限流的情况。为了达到这一点,就要求对系统的性能指标做到实时的收集和监控。
- 如果需要拒绝某些用户的请求,需要定义特定的错误码并返回,这样客户端就可以识别错误码从而得知是因为限流而导致的请求失败,然后做对应的处理,比如隔一段时间后进行重试。
- 限流可以当做系统伸缩时的临时处理措施。如果某些活动突然开始增长,并且在预期内不会持续很长时间,那么此时采用限流模式要优于系统伸缩,因为系统伸缩会极大的增加运维成本。
- 在系统进行伸缩时如果采用限流设计作为临时措施,如果活动增长过大导致资源占用急剧增加,此时可能会出现系统不可用的情况。如果这种情况是不被允许的话应该首先配置更多的资源和更加及时的自动伸缩。
- 需要确保 SLA 的系统要考虑采用限流设计
- 多租户环境中,如果需要避免某个租户占用全部资源,可以采用限流设计
- 需要应对短时间大量请求或者任务的情况
- 如果需要通过限制系统所占用的最大资源来实现成本优化也可以采用限流设计。
以上是文章的大致翻译,关于限流模式皓叔在专栏中也做了介绍 49 | 弹力设计篇之“限流设计”。
文章简要总结如下:
限流策略
- 拒绝服务
- 服务降级
- 特权请求
- 延时处理
- 弹性伸缩
实现方式
- 计数器方式
- 队列算法
- 漏斗算法(Leaky Bucket)
- 令牌桶算法(Token Bucket)
- 基于响应时间的动态限流
设计要点
- 早期考虑
- 高性能,对流量变化灵敏
- 需要有手动开关
- 限流发生时要有事件通知,告知运维人员有限流产生
- 对于拒绝掉的请求,需要返回特定的限流错误码,可以和其他错误区分开来让客户端更方便的处理
- 限流需要让后端感知到,从而决定是否要做降级
从内容上看,两篇文章提到了很多相同的地方,耗叔的文章更全面一点,给出了一些具体的实现策略,两篇文章结合起来对于限流模式的设计就应该有一个比较清晰的认识了。
Kafka Consumer 中有一个配置 max.poll.records
,表示 Kafka 每次 poll 消息的条数,之前默认有多少 poll 多少,在 0.10.1.0 版本中将默认值改为了 500,并添加了一个新的配置 max.poll.interval.ms
,表示两次 poll 消息的时间间隔,默认是 300s。这里有个问题是如果拉取的消息数过大并且消息的处理时间较长超过了 max.poll.interval.ms
的值,就会导致提交失败并发生消息回滚,最终造成消息的重复消费或者消息堆积,因此在设置 Consumer 配置时一定要注意 Kafka 消息的消费时间,保证在设置的拉取时间间隔内消费完所有的消息。
。另外推荐一篇 kafka 的文章为了追求极致的性能,Kafka掌控了这11项要领!,很赞的文章。
分享一下最近整理的关于时间管理的一些方法
这是采铜老师《精进》一书中提到的,将事情以收益值和收益半衰期两个维度进行划分,最终得出下面四个区间:
而我们做事情的原则就是尽可能的做高收益值、长半衰期的事情,如图所示:
人们最容易做的事情就是高收益值、短半衰期的事情,比如各种的娱乐、购物等。而对人生帮助最大的往往是高收益值、长半衰期的事情,比如写作、演讲,学会更好的表达沟通,更加的了解自己等,但是这些事情往往有一个特点就是成就的滞后性,也就是说这些都是需要长期积累的事情,甚至需要经历挫折和痛苦才可能最终获得收益。这就对我们提出了更高的要求,要自律,要延迟满足感,要坚持等等。关于这些又是另外的一些学习讨论了,以后在整理分享。
暗时间的概念是刘未鹏老师在《暗时间》一书中提出的概念,所谓「暗时间」指的是那些你没有正式学习工作的时候,所进行的思考的时间。别人可能在无所事事或者想一些无关紧要的事情,但你的大脑却在不停的运转思考,在吃饭走路时,甚至睡觉时你可能都在思考,这部分被利用起来的时间就是暗时间。
记得之前在一个纪录片中提到阿里云的王坚博士有时候因为思考走在路上别人跟他打招呼都没注意到,这就是典型的充分利用暗时间的例子。大多数人的工作时间是差不多的,那么对于暗时间的利用可能就会极大的影响一个人的成长。这也是为什么很多人明明没有整天工作学习,但进步确很快,因为他们在不停的思考,当别人就看个热闹时,他们在思考,在分析,日积月累成长的自然就比别人快很多。
为了充分利用暗时间,还需要注意几点
-
尽可能减少无效信息干扰。个人亲身经历,当你打完一局 LOL或者看完一部小说时,脑海中还是会回味之前的经历,还会想游戏的操作如果怎么样就更好了,还会想小说里面的情节。这会严重影响我们对暗时间的使用,我们必须学会控制信息的输入。尽可能少的输入无效和负面的信息。
-
有的放矢。大脑在自然状态下是随机漫步的,各种乱七八糟稀奇古怪的想法都会冒出来,为了利用暗时间那就必须将注意力专注于某一个想法或者问题上,比如我之前写的代码还有哪些可以优化的地方,针对某个事情思考背后的逻辑和原因等。
这是皓叔在专栏中提到的 花时间在解放自己生产力的事情上。作为一个程序员要有“懒惰”的品质,能自动化的事情尽量自动化。举个小栗子,之前自己在写新的 ARTS 时都是手动创建一个文件然后打上四部分的标题,但作为一个程序员完全可以将该过程自动化,写个简单的脚本即可:
import os
from datetime import datetime
def create_new_arts():
now = datetime.now()
print(now.date())
filename = "%s.md" % now.date()
lines = [
"### 一. Algorithm\n",
"### 二. Review\n",
"### 三. Tips\n",
"### 四. Share\n"
]
os.system("touch %s" % filename)
with open(filename,"w") as f:
f.writelines(lines)
create_new_arts()
然后结合 alias 功能创建一个新的 newarts
命令:
alias newarts='workon py3.7 && cd ~/arts/ && python ~/arts/create_new_arts.py'
这样再次创建新的 ARTS 时直接运行 newarts
命令就会自动创建一个新的文件,文件名为 当天日志.md
。
一个很小的例子,虽然每次也就节省下近一分钟的时间,但是积少成多,各个地方都节省一些,那么叠加起来的时间量就是巨大的。每天一分钟,每年也是 360 分钟,六个小时的时间。 一个厉害的人从来不对停止对优化工作生活步骤的探索。