版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mike_learns_to_rock/article/details/89077898
Algorithm
https://leetcode.com/problems/trapping-rain-water/
一道hard,不过用stack做的。
当然有好几种解法,后面我慢慢补上吧
public class P42_TrappingRainWater {
public int trap(int[] height) {
// increase: if h[i] <= h[i + 1]: drop h[i], continue;
// increase -> finding; recording left = h[i];
// finding:
// if h[i] >= h[i+1] continue;
// if h[i] < h[i+1]; right = h[i+1];
//
Stack<Integer> index = new Stack<>();
int res = 0;
for (int i = 0; i < height.length; i++) {
if (index.empty() || height[index.peek()] >= height[i]
) {
index.add(i);
} else {
int last = 0;
while (!index.empty() && height[index.peek()] <= height[i]) {
int in = index.pop();
int tmp = (height[in] - last) * (i - in - 1);
last = height[in];
if (tmp > 0) {
res += tmp;
}
}
if (!index.empty()) {
int in = index.peek();
int tmp = (height[i] - last) * (i - in - 1);
if (tmp > 0) {
res += tmp;
}
}
index.add(i);
}
}
return res;
}
}
Review
Design a system that scales to millions of users on AWS
这篇适合多读几次。
题目解读:如何设计可扩展的百万用户的图片视频网站?(忽略AWS)
约束:网站从1个用户 - 一千万用户(10亿写请求/月,1000亿读请求,每个写数据是1KB);
所以:
- 1TB数据/月(10亿大概是2^31,1024是2 ^10,所以1KB * 1024 * 1024 * 1024 = 1TB)
- 400 写/秒
- 40,000 读/秒
架构
0: DNS + WEB_SERVER + MYSQL
- 1: DNS + WEB_SERVER + MYSQL + 对象存储
- 2: 高可用和副本:DNS + LB + WEB_SERVER(反向代理) + 写API + 读API + MYSQL_MASTER + MYSQL_SLAVE + CDN + 对象存储
- 3:读性能瓶颈:+ memory cache集群(mysql的热点数据以及web server的session数据) + MYSQL_READ副本
- 4:自动扩缩容,运维以及监控
- 5: 写存在瓶颈:+ nosql + 消息队列 (异步处理写入请求,例如上传图片然后异步压缩图片)
诊断工具很重要,可以及时发现瓶颈;
一定规模后日志分析以及监控运维,弹性集群管理都是问题;
缓存以及存储的高可用有很多东西需要挖掘。
附录:
1 request per second = 2.5 million requests per month
40 requests per second = 100 million requests per month
Tips
- JVM:java8 默认垃圾回收器是Parallel GC,建议使用G1,jvm参数-XX:+UseG1GC
- effective java:java中尽量使用静态工厂方法来创建对象;好处是1.命名清晰 2.避免重复创建对象(享元模式) 3.返回多个子类型;
享元模式
- 用共享的方式加速大量细粒度对象的创建/获取,这些对象中一部分的内部状态是相同的,可以共享的。https://en.wikipedia.org/wiki/Flyweight_pattern(里面的java例子,就是多次访问对象,最后很多部分是共享的。)
- 享元对象,内部状态(对象共享),外部状态(每个享元对象的外部状态不同,不共享)
- 实例:java利用缓存加速大量小对象的访问时间:java.lang.Integer#valueOf(int)
Share
jvm垃圾回收简介
- 运行时数据区域
- 程序计数器:记录当前线程的正在执行的字节码指令地址(如果是native方法,则值为空)
- Java 虚拟机栈:每个java方法执行的依赖的局部变量,操作数和常量池引用:-Xss(StackOverflowError/OutOfMemoryError)
- 本地方法栈:类似上面
- 堆:线程共享区域,主要的GC区域;新生代和老年代;物理内存不需要连续;
- -Xms 堆大小初始值
- -Xmx 堆大小最大值(OutOfMemoryError)
- 方法区
- 用于存放已被加载的类信息、常量、静态变量等数据。
- 从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
- 垃圾回收
- 垃圾收集主要是针对堆和方法区进行
- 如何判断对象可以回收
- 引用计数法:循环引用导致计数器无法为0,JVM不实用引用计数
- 可达性分析算法:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。
- 方法区回收:因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。
- finalize(): 最开始兼容C++,类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好;该方法可认为被废弃了。
- 引用类型:判断回收的依据
- 强引用:不会回收
- 软引用:内存不够的情况下才会被回收
- 弱引用:一定回收,只能存活到下一次垃圾回收发生之前
- 虚引用:一定回收,唯一目的是能在这个对象被回收时收到一个系统通知,诊断系统使用
- 垃圾回收算法
- 标记-清除:最原始的算法,两个阶段效率都低,内存碎片问题
- docker distribution里默认是这种方法GC镜像layer,当镜像很多时,标记时间特别长。不建议使用。
- 标记-整理:解决内存碎片问题,但是需要移动大量对象,效率低
- 复制:AB区域,A的存活对象复制到B区域,然后清除A;缺点是内存浪费;
- 扩展:docker distribution里建议的这种方法GC镜像layer,需要把存储设置为只读模式,然后切换存储。不建议使用。
- 分代收集
- 新生代:复制算法, Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。
- 老年代:标记-清除/标记-整理
- 标记-清除:最原始的算法,两个阶段效率都低,内存碎片问题
- 垃圾回收器
- server强调停顿时间短,后台运算任务强调高吞吐量,希望尽快计算结束;
- 缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
- serial 收集器:串行 + 单线程
- Client 场景下的默认新生代收集器,小内存场景,停顿时间较短
- parnew 收集器:serial多线程版本
- 它是 Server 场景下默认的新生代收集器
- 能与 CMS 收集器配合使用
- parallel scavenge 收集器:多线程 + 自适应GC策略
- CMS(并发标记清除)收集器:基本不需要停顿
- 并发标记和并发清理和用户线程一起运行,基本不需要停顿
- 缺点明显:吞吐量低,无法处理浮动垃圾,内存碎片可以提前触发full gc
- G1收集器:服务端的首选
- 其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。
- 堆重定义为多个region,新老代物理不隔离;空间整合,无内存碎片
- Set数据结构,提高可达性分析的效率,避免全堆扫描
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
- 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。