学习日报 0821 Effective Java + 场景 + 代码随想录

Effective Java:

第1条:用静态工厂方法代替构造器

示例:将boolean基本类型转换成了一个Boolean对象引用:

public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;
}

这里的静态工厂不同于设计模式的工厂方法

静态工厂方法对比构造器的优缺点

优点:

1.有名称,可以确切的描述正被返回的对象

2.不必每次调用都创建一个新对象,可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。(为重复的调用返回相同的对象)

3.可以返回原返回类型的任何子类型的对象,这种灵活性的应用是API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁。这项技术适用于基于接口的框架,因为在这种框架中,接口为静态工厂提供了自然返回类型。

4.所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。

5.方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。

这种灵活的静态工厂方法构成了服务提供者框架的基础,例如JDBC API。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。

对于JDBC来说,Connection就是其服务接口的一部分, DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

缺点:

1.类如果不含公有的或者受保护的构造器,就不能被子类化。要想将Collections Framework中的任何便利的实现类子类化,这是不可能的。但这样会因祸得福,因为它鼓励程序员使用复合,而不是继承,这正是不可变类型所需要的(不可变类:实例不能被修改的类,每个实例中包含的所有信息都必须在创建时就提供,并在整个对象的生命周期内固定不变,比如String、基本类型的包装类、BigInteger、BigDecimal)

2.程序员很难发现它们。在API文档中,它们没有像构造器那样在API文档中明确标识出来。

场景问题:

一、设计一个订单号生成服务

1.唯一性:UUID 雪花算法

2.数据量:设计时需要充分考虑后续数据量变大的兼容问题,需要提前预留足够的位数

3.可读性:订单号应该易于理解和记忆,可以根据业务需求自定义订单号格式和组成方式,比如时间戳、用户id

4.基因法:设计时将和分表有关的字段编码到订单号中,比如买家ID

5.可扩展性:订单号生成服务需要支持高并发、分布式部署、横向扩展等特性,可以采用分布式ID生成器、Redis来实现。

6.高性能:内存缓存、异步处理

7.高可用:多节点部署、负载均衡、健康检查

比如

扩展:

UUID:

简单总结一下,Version 1和Version2这两个版本的UUID,主要基于时间和MAC地址,所以比较适合应用于分布式计算环境下,具有高度唯一性。

Version 3和 Version5 这两种UUID都是基于名称空间的,所以在一定范围内是唯一的,而且如果有需要生成重复UUID的场最的话,这两种是可以实现的。

Version 4 这种是最简单的,只是基于随机数生成的,但是也是最不靠谱的。适合数据量不是特别大的场景下

雪花算法:

是由twitter研发的一种分布式ID生成算法,可以生成全局唯一且递增的ID。他的思想是将一个64位的ID划分成多个部分,包括时间戳、数据中心表示、机器标识和序列号等。

其中时间戳位于id的最高位,保证新生成的ID比旧的ID大,在不同的毫秒内,时间戳不一样。(如果出现一毫秒内生成多个id的情况,通过引入序列号来解决,每次生成id时序列号自增)

优点:

1.高性能高可用:生成时不依赖于数据库,完全在内存中生成。

2.高吞吐:每秒钟能生成数百万的自增ID

3.ID自增:在单个进程中,生成的ID是自增的,可以用作数据库主键做范围查询,但是在集群中是没办法保证一定顺序递增的

缺点:

  1. 每个节点的机器ID和数据中心ID都是硬编码在代码中的,并且全局唯一,当某个节点出现故障或需要扩容时,就需要更改其对应的机器ID或数据中心ID,但是这个过程比较麻烦,需要重新编译代码,重新部署系统。另外,当某个节点的机器ID或者数据中心ID被设置成了已经被分配的ID,那么就会出现重复的ID,这样会导致系统的错误和异常。
  2. 雪花算法中需要使用zookeeper来协调各个节点的ID生成,但是zookeeper的部署成本大

3.依赖系统时间的一致性,如果系统时间回拨,或者不一致,可能造成ID重复。(解决时间回拨问题,最简单的是发现时钟回调,直接抛异常。另外一种是发现时钟变小就拒绝ID生成请求,等到时钟恢复到上一次的ID生成时间后,再开始生成新的ID,美团Leaf引入了Zookeeper来解决时钟回拨问题,其大致思路为:每个Leaf运行时定时向zk上报时间戳。每次Leaf服务启动时,先校验本机时间与上次发ID的时间,再校验与zk上所有节点的平均时间戳。如果任何一个阶段有异常,那么就启动失败报警。)

分表字段的选择:

通常用买家ID分表,可以避免数据倾斜(性能瓶颈、资源利用不均、查询效率低下),在生成订单号时,一般会把分表结果编码到订单号中,比如买家ID的路由结果1023,直接作为一段固定的值放到订单号里就行了,这就是基因法。

如果没有买卖家ID怎么办:

低频查询或非核心功能查询,可以用ES等搜索引擎解决。

二、如何设计一个购物车功能

主要功能:在用户选购后下单前,把用户的意向商品、数量等信息保存下来、方便统一支付。

主要操作:加入购物车、查看购物车、通过购物车下单。

一般我们在存储时并不需要把所有商品信息都保存,只需要保存一个SKUID就可以,再加上数量、时间等字段。至于商品的库存、价格、介绍等信息,只需要在渲染购物车时实时反查和计算就行。

对于电商平台来说,用户一般分为未登录和登录两种状态。

未登录用户

对于未登录用户,其实他的购物车的信息没必要存储在后端,只需要在客户端做临时缓存就行了。客户端存储可以选择Cookie 和LocalStorage等技术。在存储时,只需要设计一个JSON格式就可以了,因为用户没登录,所以也就不需要标识数据属于谁

已登录用户

对于已经登录的用户的购物车,我们就不能存储在客户端了,因为客户端的数据可能会超时、一旦换了设备也就没有了。我们需要用持久化存储,那么就可以使用数据库和Redis缓存。

如果是使用数据库,那么就直接建表存储就行了,表中主要需要包含user id、sku id、count、time stamp等几个业务字段就可以了。这样每一个加过购物车的用户都有一条记录。

如果使用Redis来保存的话,其实也简单,只需要在上面的未登录用户的购物车的基础上增加一个user id作为key就行了

扩展:

使用Redis和数据库存储各有好处:

  1. Redis 性能要比 MySQL高,响应时间更短,可以支撑更多的并发请求

2.MySQL的数据可靠性是要好于 Redis 的 ,因为 Redis 是异步刷盘,如果出现服务器掉电等异常情况,Redis 是有可能会丢数据的。

3.MySQL的另一个优势是,它支持丰富的查询方式和事务机制

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

首先,我们需要问清楚,一天100W次的登录,在一天内有没有某个时段是高峰的?高峰期的QPS大概可以达到多少。

如果没有高峰期,虽然100万听上去挺多的,但是其实平均下来一秒钟的QPS也就10,这个量的话,其实根本不需要做什么特别的JVM优化。

一般业务场景中,都是有自己的业务高峰期的,比如电商业务基本上上午十点和下午两点是业务高峰期,基本上这时候的QPS是平时的20倍都不止。

我们假设登录业务存在高峰期,峰值时长大概持续1个小时,峰值的QPS可以达到200。那么需要做哪些优化?

作为一个登录服务,一般来说我们在接收到请求之后,只需要给用户进行鉴权并把结果返回给前端就行了。在这个过程中一般不太会去查询太多的数据,比如权限什么的也都是在后面访问页面再查询的。所以,峰值200左右的QPS,对于JVM的内存来说,最主要的就是会因为远程调用,而创建出很多请求参数和请求的响应。而这些对象基本都是朝生暮死的,接口调用结束之后就会被回收掉并且通常来说这些对象也不会很大,因为登录并不是注册,其实并不携带特别多的信息,那么也就是说,会产生大量的小对象,即新生代会不断的创建对象并被回收掉

JVM调优:

1.堆内存设置:

当我们机器只有4核8G 堆内存大小的设置不能太大,要给其他应用预留一部分。所以一般把JVM的堆内存设置成操作系统内存的一半,也就是4G。至于初始内存和最大内存,建议设置成一样的,可以避免JVM在运行过程中频繁进行内存扩容和收缩操作,提高应用程序的性能和稳定性。

-Xms4G

-Xmx4G

2.垃圾回收器的选择:

经过分析,这个业务中频繁在新生代创建并销毁对象,那么就意味着新生代的GC比较频繁,所以应该选择一种GC过程中STW时间短的,并且在年轻代的回收中也能发挥效果的。

在新生代的垃圾收集器中,主要以Serial、ParNew、ParallelScavenge以及支持整堆回收的G1了。

因为新生代采用的都是复制算法,所以不太需要考虑碎片的问题,我们主要考虑吞吐量和STW的时长就行了,

首先排除单线程的Serial,剩下ParNew是一个并发的收集器,ParallelScavenge更加关注吞吐量,而G1作为JDK9中默认垃圾收集器,他不仅同时具有低暂停时间和高吞吐量的优点,但是他对内存有要求,最小要4G,

从使用门槛上来说,G1是可以用的,因为一般来说,内存要大于等于4G的话,才适合使用G1进行GC。

所以,我们采用G1作为垃圾收集器

-XX: +UseG1GC

在使用G1之后,其实他自己是有一套自动的预测和调优机制的。我们只需要通过参数来设置最大停顿时间就行了,一般建议设置到100-200之间,一般不会被用户感知

-XX:MaxGCPauseMillis=200

其次我们还可以调节一些G1的配置,比如GC线程数,可以先配置4个,后续根据实际情况调整:

-XX:ParallelGCThreads=4 设置并行GC线程数为4

-XX:ConcGCThreads=2 设置并发GC线程数为2

并发是一个CPU处理器同时处理多个线程任务。(宏观上是同时处理多个任务,微观上其实是CPU在多个线程之间快速的交替执行。操作系统中有一个组件叫做任务调度器,它将CPU的时间片(windows下时间片最小约为15毫秒)配给各个线程使用,在一个时间段的线程运行时,其他线程处于挂起状态,这种就称之为并发。)

并行是多个CPU处理器同时处理多个线程任务。(当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这就被称之为并行。)

3.各区的大小设置:

-XX:G1NewSizePercent 设置年轻代的初始大小 默认5%

-XX:G1MaxNewSizePercent 设置年轻代的最大大小 默认60%

对于我们的业务场景 5%太小了 可以调整到30%

-Xx:G1HeapRegionsize=2m:将 G1 的区域大小设置为 2MB,以提高垃圾回收的效率和精度。

-xx:G1NewsizePercent=20:设置年轻代的初始大小为堆的20%。

-XX:G1MaxNewsizePercent=50:设需年轻代的最大大小为堆的50%。

-XX:G10ldcsetRegionThresholdPercent=10:设置老年代的大小为堆的10%。

-XX:G1HeapWastePercent=5:设置垃圾回收后留下的未使用区域的最大比例为5%。

4.添加必要的日志:

因为以上配置都是根据业务大致分析出来的初始配置,所以我们一定是需要不断地调优的,那么必要的日志相关参数就要添加。如:

-XX:MaxGCPauseMillis=100:最大 Gc 暂停时间为 100 毫秒,可以根据实际情况调整;

-XX:+HeapDumpon0utofMemoryError:当出现内存溢出时,自动生成堆内存快照文件;

-XX:HeapDumpPath=/path/to/heap/dump/file.hprof:堆内存快照文件的存储路径;

-XX:+PrintGc:输出 GC 信息;

-XX:+PrintGcDatestamps:输出GC发生时间;

-XX:+PrintGcTimestamps:输出GC发生时JVM的运行时间;

-XX:+PrintGcDetails:输出GC 的详细信息;

-Xlog:gc*:file=/path/to/gc.log:time,uptime:filecount=10,filesize=108M:将GC 日志输出到指定文件中

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

正常情况:业务有起色或业务蹭到某个热点

异常情况:被DDOS攻击

如果蹭到某个热点,就通过临时方案解决,最简单的就是扩容,增加集群的服务器数量,提升机器的硬件资源配置,让整体的吞吐量提升。

如果是业务自然增长,就需要考虑设计一个高并发系统

扩展:

防范DDOS攻击

1、如果可以识别出攻击源,如机器IP等,可以在防火墙服务器上放置一份 ACL(访问控制列表) 来阻断这些来自这些 IP 的访问。

2、对于带宽消耗型攻击,最有效的办法那就是增加带宽

3、提高服务器的服务能力,增加负载均衡,多地部署等。

4、优化资源使用提高 web server 的负载能力。例如,使用 apache 可以安装 apachebooster 插件,该插件与varnish 和 nginx 集成,可以应对突增的流量和内存占用。

5、使用高可扩展性的 DNS 设备来保护针对 DNS 的 DDOS 攻击。可以考虑购买 Cloudflare 的商业解决方案,它可以提供针对 DNS 或 TCP/IP3 到7层的 DDOS 攻击保护。

6、启用路由器或防火墙的反IP欺骗功能。

7、付费,使用第三方的服务来保护你的网站。

8、监控网络和 web 的流量。时刻观察流量变化

代码随想录:

  • 242.有效的字母异位词
  • 349. 两个数组的交集
  • 202. 快乐数
  • 1. 两数之和
  • 454.四数相加II
  • 383. 赎金信
  • 15. 三数之和
  • 18. 四数之和
  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值