架构的优化就是一层加一层
当耦合性太高的时候,就加一层,作为缓冲。 当合并有单点的时候,就分开。当分开有不能统一的时候,就合并。
单体应用转向微服务本质
单一职责,关注点分离,业务解耦,服务隔离,技术异构,简化部署,易于伸缩扩容
本质:其演变最重要的驱动力时方便某个服务顺利的“死去”与“重生”。面向失败设计。
方便某个服务的 死去,重生。对应的快速发布、服务淘汰、服务自动故障恢复。
微服务治理组件
强端点若管道(SOA -> 微服务)
/cgi-bin
{"data":{}'}
ISO 8601
标准本文转载自 ThoughtWorks 洞见。
原文链接:
其中迪米特原则又叫最少知识原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息。还有一个更简单的定义:只与直接朋友通信。
在平时开发中,在类,方法的设计上也尽量保持这个原则。A依赖B对象,A不直接调用B对象中依赖的对象,但完全参考这个原则开发,会导致类内部方法的膨胀。因此在设计需要自己做出权衡,但是其中的平衡点并不好找。后面读了《代码简洁之道》第六章中也有提到迪米特原则,说明了某些场景并不需要遵守这个原则,或者说该原则不适用这种场景,例如:A类依赖B类,B类可能是个数据结构,无对象行为,那么直接调用B内部的数据是可以的。然而在 Java 中因为定义私有变量的原因(内聚、封装),提供了大量的访问方法(get)、该值方法(set)
其中还提到了 隐藏结构
,接着上面那个例子,假设 A 依赖 B,B就是具有行为的对象,我们在定义 B 对外的行为,应该更加符合我们系统的业务行为。比如:
1 | // A 拿到的B的某个值(可能在B内部经过大量计算) |
这样满足了 迪米特原则,也不会导致 B 对象暴露内部过多的数据结构。
用心
,用心 便会思考命名、抽象、充分酌量架构的取舍,用心便会写出好的代码。重构不是把整个系统推到重来,把一个功能代码重新设计也是重构,提取一个方法也是重构。
重构是研发最重要的能力,重构的目的就是让代码符合设计原则,例如:单一原则。所有的方法可以是单一职责,即使是复杂的业务方法也是单一职责,那就是分发与聚合,典型的便是策略模式。
我的理解:抽象的本质是找事务的共性,找到共性便能抽取、抽象。
之前做的导出报表的功能,Release 有 操作时间、操作人、操作类型,Confirm 也有操作时间、操作人、操作类型(approve同理),这里就能找到Release、Confirm、Approve针对报表导出这个功能的共性,进行抽象
之前有个项目需要导出很全面的报表、还存在汇总计算的需求。因为当时项目牵扯到多方的数据源,如果实时去查询多个数据库聚合会导致接口响应慢,同时存在数据量大的情况甚至会拉垮服务。
我们当时想了一套方案:建立一个新的数据源(MongoDB),通过 ETL 清洗一份新的数据结构存储在数据源。
这里存在数据同步实时的问题,我们使用 Job 去同步新的数据源数据则会存在不准确。正对这个问题我们又想到 Flink 分布式实时处理引擎。
仔细一想,我们需要实时吗?我们沟通了需求,报表的内容一般是只会查看上个月的项目信息。因此实时是个伪需求,不要为了用新技术,而新技术,技术是用来服务业务的。
推荐使用 @Resource ,@Resource 支持 name type,默认 name 注入,同时 它是 JDK 的注解,减少依赖
单一职责:指的是如果一个类的依赖太多,构造方法就会显得很臃肿,这时我们就得思考当前类是否承担了太多职责。假如使用 @Autowired 注入 field 可能就不会发现问题。
依赖不可变:只用使用构造方法注入才能将字段定义成 final
依赖隐藏:清晰的知道类需要什么
降低容器耦合度:使用构造方法注入他只是一个普通的类,不依赖 DI 容器。如果没有容器耦合,我们可以将该类交给容器管理也可以自己管理,同时切换 DI 框架也很方便。
是否设置了使用外部存储,是则使用外部存储,否 则判断是否开启内嵌数据源存储,默认单机模式则开启内嵌数据源存储,若没有开启则自动升级为外置数据源存储,和之前一样
// 内嵌数据源
LocalDataSourceServiceImpl
// 外部数据源
ExternalDataSourceServiceImpl
RequestHandlerRegistry
注册定义的容器中的 RequestHandler
NacosMeterRegistryCenter
基于 CompositeMeterRegistry
(micrometer)实现NotifyCenter 通知中心
Cache ,CacheBuilder 可以构建:SimpleCache 、LruCache、SynchronizedCache、AutoExpireCache
username.github.io
,例如我:izachwei.github.iousername.github.io
访问成功1 |
|
1 |
|
1 |
|
1 |
|
docker run -d –name ggr -p 8088:4444 -e TZ=America/Los_Angeles -v /etc/grid-router/:/etc/grid-router/akamai/:ro aerokube/ggr:1.5.4
docker kill -s HUP ggr
String:SDS
字典(dict):ziplist、hash
list:ziplist、双向链表
set:整数集合(int[])、hash
zset:ziplist、跳表
Redis 事务不支持原子性,持久性也需要 AOF 配置的支持,修改成 always
支持的数据结构丰富
基于内存操作、响应快
支持主从、分布式满足高可用
读写数据是单线程操作、避免了上下文切换 保证了原子性
支持持久化操作、RDB AOF
数据库容量受到物理内存的限制,内存资源较为珍贵,支持少量数据的读写,Redis适合热点数据、较少的数据量上的读写操作。
主机宕机,主从同步之间存在延时,会导致丢数据
在线扩容很复杂
按照上述四个方面进行方案设计:
大文件、网络传输
大文件传输分为 数据同源 和 异构 两种方案
异构则需要考虑存储协议的定义优化 (序列化、反序列)
存储协议 -> 网络协议 -> 流式处理
考虑异常、断点续传、保证完整性
文件保存的安全性:备份?读取文件考虑效率?
Client 、Server 、存储层、Manager(分片信息管理 、聚合、分布式存储等)
大文件存储肯定是尽量打散、降低单节点的网络压力、每个节点内部根据数据类型管理他的内存存储结构(avro(序列化系统)、parquet(列示存储))、使用分布式文件存储系统:HDFS
客户端拆分大文件生成多个文件分片,调用服务端生成一个文件上传任务(分片相关信息)返回本次分片上传唯一标识
发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
要求点对点传输、基于 TCP 长连接流式传输,服务端 buffer -> flush ,利用 buffer 建立可监控、可控速的网络流传输。生产 消费模型。
为了保证内存不溢出、使用有有界内存队列 BlockingArrayQueue ,控制网络流传输速率,写入 系统缓存 ,定时统一写入数据库。网络IO
如何实现断点续传
一般实现方式有两种:
- 服务器端返回,告知从哪开始
- 浏览器端自行处理
上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可
如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可
分片写 、多线程 、切片传输(压缩数据、考虑异常、断点续传、保证完整性)、检验 SHA1、UDP 分片 分组、考虑网络I/O 磁盘I/O 瓶颈
网络的传输考虑使用 Netty 框架 NIO
异常情况的回滚 :使用数据库的事务,每个大文件的接收,创建一个大事务、每个分片写入数据库、存在问题回滚大事务
分片:
定义分片信息的数据结构
分片要考虑网络的实时传输 动态改变分片大小
为什么选择 TCP ?
TCP 面向字节流、可靠
丢包?数据传输异常中断?
SHA1 校验 文件内容完整性、重传
易用性:工具/平台的上手难度,使用复杂度应该尽可能的低,因为自动化测试的目的是提效人力,而不是增加人力负担。
平台支持:移动端至少需要覆盖移动端和网页端双平台,同时基于外卖的业务特点,不仅需要对Native支持,也需要支持Mach(自研局部动态化框架)、H5、React Native、美团小程序等技术栈。
稳定性:自动化测试用例的执行需要有足够的稳定性和准确性,测试过程中不应因测试工具本身的不稳定而出现稳定性问题。
维护成本:维护成本很大程度上决定了测试工作量的大小,因需求产生变动或架构重构等问题时,用例的维护成本应该尽可能的小。
可扩展性:当测试方案不能满足测试需求时,工具/平台应具备可扩展的能力。