春雷行动-前端技术之CSS必备知识

前言

这篇文章不是教程,也不是让读者能够学会和掌握CSS,而是我的学习笔记。文章的目的是让我实践,系统的了解一下一些必备知识,仅此而已。

如果你需要这方面的知识详细学习的话,推荐你看参考的链接。内容全部来源于此。

正文

五种经典布局

1.空间居中

1
2
3
4
.parent{
display: grid;
place-items: center;
}

核心代码是place-items属性,那个是它的简写形式place-items: <align-items> <justify-items>; 两者相同的话可以省略。

左上角 place-items: start;

右下角place-items: end;

2.并列式布局

并列式布局就是多个项目并列。容器设置成 Flex 布局,内容居中(justify-content)可换行(flex-wrap

1
2
3
4
5
.container{
display: flex;
justify-content: center;
flex-wrap: wrap;
}

项目上面只用一行flex

1
2
3
.col{
flex: 1 1 300px;
}

flex属性是flex-growflex-shrinkflex-basis这三个属性的简写形式 flex: <flex-grow> <flex-shrink> <flex-basis>;

  • flex-basis:项目的初始宽度。
  • flex-grow:指定如果有多余宽度,项目是否可以扩大。
  • flex-shrink:指定如果宽度不足,项目是否可以缩小。

flex: 0 1 300px; 表示项目初始宽度是300,宽度不够的话可以缩小,但是不可以扩大。flex: 1 1 300px;,表示项目始终会占满所有宽度。

阅读全文 »

Newegg的事件中心之Event Hub

万事皆事件

事件是信息的一种承载媒介,描述特定对象某一瞬间的非持续性变化,与唯一时刻和唯一对象关联。例如:某台计算机从运行状态变更为关机,程序运行开始和结束,办公大楼停电等。事件是对象在两个不同状态中的变更瞬间的记录。

对于事件,我们关注时间点,什么事件,什么状态。在企业中存在大量的事件,系统事件,监控事件,业务事件等,通过对事件的治理和挖掘,能够发现很多价值,解决切实的痛点。基于以上思考,我们构建了Event Hub。

Event Hub简介

Event Hub是一个高度可缩放、分布式、基于时间序列的事件中心,能够实时的处理流式事件并进行告警和提醒。

Event Hub作为Newegg事件信息中枢,产品化新蛋各产品资源及平台底层基础设施服务生命周期与运转中的重要事件信息,并构建完善的事件消费渠道与流程,支撑线上监控与运维。

Event Hub产品化提供的事件信息,由Newegg内部各产品模块与底层基础设施服务获取,经过聚合,判定和收敛再最终呈现。信息源来自各模块底层的系统日志与监控项,保障客户透传客户的信息准确性与价值。

目前应用场景

企业级监控/告警平台

在Event Hub之前公司监控存在一些问题:

  • 告警不可追溯
  • 告警不可指派
  • 状态可变更很弱
  • 监控信息可视化很弱
  • 没有更好的统计报表

为了解决以上问题,治理企业级监控问题,我们在Event Hub中基于现存的问题,构建了企业级监控平台。俗话说,先挑软柿子捏。

作为企业级监控平台,Event Hub 立足于能够助力发现、定位、解决问题,保障系统与服务整体的稳定与性能。引入事件作为监控的信息载体,能更准确与直接描述资源底层基础设施服务的运行状态,助力更高效发现、定位从而解决问题。致力于提交信息描述准确性,减少延迟,传递更多的信息,完善监控信息维度,使用通用事件引擎对告警类信息加工处理,尽而告警。

1611048694361

阅读全文 »

Java NIO扫盲篇

概述

对于java世界,想了解高性能网络编程。那么就必须了解NIO,和常用的网络编程框架,以及高性能的网络编程模式。这里面缺一不可,本篇只是管中窥豹,将自己学习的过程和笔记记录下来,希望给工作中很难接触这方面的同学带来一点点帮助!

Java NIO是什么?

NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 ChannelSelectorBuffer 等抽象。

NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

NIO 的基本流程

通常来说 NIO 中的所有 IO 都是从 Channel(通道) 开始的。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

NIO 核心组件

NIO 包含下面几个核心的组件:

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。

如何使用?

内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。

下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。

1
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

这里多说一句,在Kafka源码中索引就大量使用了内存映射文件。

文件NIO

读文件

读取文件涉及三个步骤:(1) 从 FileInputStream 获取 Channel,(2) 创建 Buffer,(3) 将数据从 Channel 读到 Buffer

第一步:获取通道

1
2
FileInputStream fis = new FileInputStream("C:\\demo.txt");
FileChannel fileChannel = fis.getChannel();

然后创建Buffer,这里选择的间接缓冲

1
2
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 ); 直接缓存,速度更快,JVM层面尽肯能避免多次拷贝

最后将数据从 Channel 读到 Buffer

1
fileChannel.read(byteBuffer)

写文件

几乎和读文件一样。

首先从 FileOutputStream 获取一个通道

1
2
FileOutputStream fos = new FileOutputStream("C:\\demo.txt");
FileChannel channel = fos.getChannel();

下一步是创建一个缓冲区并在其中放入一些数据

1
2
3
4
5
6
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<byteBuffer.limit(); ++i) {
buffer.put( byteBuffer.get(i));
}

buffer.flip();

最后一步是写入缓冲区中:

1
channel.write(buffer);

套接字NIO

NIO 实现了 IO 多路复用中的 Reactor 模型

  • 一个线程(Thread)使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
  • 通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。

创建选择器

1
Selector selector = Selector.open();

将通道注册到选择器上

1
2
3
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);

注册的具体事件,主要有以下几类:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

监听事件

1
int num = selector.select();

使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。

处理事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (true) {
int num = selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
}

高性能网络编程

Reactor模式

Reactor模式+任务池模式

多Reactor模式

参考

  1. Java NIO

  2. Scalable IO in Java

  3. 《Scalable IO in Java》笔记

只是记录自己不知道的知识点,或者经常容易忘的内容,想知道更多的信息建议看课程或者找我聊天交换技能。在课程的内容基础之上,补充了一些自己知道的知识点。

Kafka的认知

  • 分布式消息引擎平台
  • 分布式实时流式处理平台

早期Kafka社区对Kafka的定位为⼀个分布式、分区化且带备份功能的提交⽇志(Commit Log)服务,近期在官网彻底更改为分布式实时流式处理平台。

Kafka流式处理框架的优势

  • 更容易实现端到端的正确性(Correctness)
  • 轻量型,嵌入式流式计算的定位

避免不必要的Rebalance

  • session.timeout.ms
  • heartbeat.interval.ms
  • max.poll.interval.ms
  • GC参数

session.timout.ms决定了Consumer存活性的时间间隔

heartbeat.interval.ms决定存活心跳发送间隔。

max.poll.interval.ms 它限定了Consumer端应⽤程序两次调⽤poll⽅法的最⼤时间间隔。

消费者TCP管理

消费者实例在KafkaConsumer.poll建立TCP连接,主要分为3类连接:

  1. 确定协调者和获取集群元数据。
  2. 连接协调者,令其执⾏组成员管理操作。
  3. 执⾏实际的消息获取。

第一类连接仅在开始前建立,稍后(第三类创建成功)就会销毁,consumer实例会长期保留2,3类连接。

Consumer实例会长期建立broker数量(分区所在broker数量)+1个连接。

TCP连接的三个时机:

  1. 发起FindCoordinator请求时
  2. 连接协调者时
  3. 消费数据时

何时关闭TCP连接:

  1. ⼿动调⽤KafkaConsumer.close()⽅法
  2. 执⾏Kill命令
  3. Kafka⾃动关闭(是由消费者端参数connection.max.idle.ms控制的,该参数现在的默认值是9分钟)
阅读全文 »

概要

记录线上Zookeeper集群和Kafka集群部署过程,操作系统配置,以及一些参数的设置。给大家部署提供一些宝贵意见和参考。部署一个集群,按照官方社区的文档,很容易就搭建一个集群,但是为了更好的发挥集群的性能,有很多设置是可以避免产生不必要的问题,都是在惨痛的教训中产生的经验。

本文内容来自NeweggConfluent 产线上Kafka cluster运维经验,仅供参考。

Newegg 产线Kafka版本选择Confluent发行版本,版本对照表:

Confluent Platform Apache Kafka
5.5.x 2.5.x

Docker镜像列表:

Service Info
Zookeeper confluentinc/cp-zookeeper:5.5.1
Kafka confluentinc/cp-kafka:5.5.1

Zookeeper Cluster

硬件

内存至少4GB,Zookeeper对swap敏感,应当避免swap。

集群配置

zookeeper节点数据应该为2n+1,n大于0,保持集群中超过一半节点存活。

Confluent docker 镜像参数设置是在ENV(环境变量)中设置,以ZOOKEEPER开头,以_替换.

参数清单

1
2
3
4
5
6
7
8
9
10

- ZOOKEEPER_SERVER_ID=1
- ZOOKEEPER_TICK_TIME=2000
- ZOOKEEPER_CLIENT_PORT=2181
- ZOOKEEPER_INIT_LIMIT=10
- ZOOKEEPER_SYNC_LIMIT=5
- ZOOKEEPER_SERVERS=192.168.0.1:2888:3888;192.168.0.2:2888:3888;192.168.0.3:2888:3888
- ZOOKEEPER_AUTOPURGE_SNAP_RETAIN_COUNT=5
- ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL=10
- KAFKA_HEAP_OPTS=-Xmx4G -Xms4G

ZOOKEEPER_AUTOPURGE_SNAP_RETAIN_COUNT控制快照的数量,ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL控制清除快照的时间间隔,默认不清除,这个很重要。

文件映射

Host volume Container volume
/opt/app/cp-zookeeper-5.5.1/data /var/lib/zookeeper/data
/opt/app/cp-zookeeper-5.5.1/log /var/lib/zookeeper/log
/etc/localtime /etc/localtime
阅读全文 »

前言

本篇文章介绍生成一个自签名SSL证书以及使用Nginx docker 代理一个https服务。

SSL证书验证安全连接,有两种验证模式:

  1. 仅客户端验证服务器的证书,客户端自己不提供证书;
  2. 客户端和服务器都互相验证对方的证书。

显然第二种更安全,一般web采用第一种,比较简单。

创建自签名证书

创建步骤

  1. 创建Key;
  2. 创建签名请求;
  3. 将Key的口令移除;
  4. 用Key签名证书。

创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/sh

# create self-signed server certificate:

read -p "Enter your domain [www.example.com]: " DOMAIN

echo "Create server key..."

openssl genrsa -des3 -out $DOMAIN.key 2048

echo "Create server certificate signing request..."

SUBJECT="/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=$DOMAIN"

openssl req -new -subj $SUBJECT -key $DOMAIN.key -out $DOMAIN.csr

echo "Remove password..."

mv $DOMAIN.key $DOMAIN.origin.key
openssl rsa -in $DOMAIN.origin.key -out $DOMAIN.key

echo "Sign SSL certificate..."

openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt

echo "TODO:"
echo "Copy $DOMAIN.crt to /etc/nginx/ssl/$DOMAIN.crt"
echo "Copy $DOMAIN.key to /etc/nginx/ssl/$DOMAIN.key"
echo "Add configuration in nginx:"
echo "server {"
echo " ..."
echo " listen 443 ssl;"
echo " ssl_certificate /etc/nginx/ssl/$DOMAIN.crt;"
echo " ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;"
echo "}"

执行以上脚本会生成4个文件:

核心配置

1
2
3
4
5
6
7
8
9
10
11
 server {
listen 443 ssl;
server_name localhost;

ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;

location / {
proxy_pass http://localhost:8866/;
}
}

完整配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
index index.html index.htm;

server {
listen 80;
server_name localhost;

location / {
proxy_pass http://localhost:8866/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

server {
listen 443 ssl;
server_name localhost;

ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;

location / {
proxy_pass http://localhost:8866/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cookie_path / "/; httponly; secure; SameSite=NONE";
}
}
}

启动nginx容器

1
docker run --name my-custom-nginx-container -p 443:443 -v "F:\docker-workspace\nginx-demo\nginx.conf:/etc/nginx/nginx.conf:ro" -v "F:\docker-workspace\nginx-demo\www.example.com.crt:/etc/nginx/www.example.com.crt" -v "F:\docker-workspace\nginx-demo\www.example.com.key:/etc/nginx/www.example.com.key" -d nginx

参考

  1. 给Nginx配置一个自签名的SSL证书

Kafka Summit 2020 -Apache Kafka - The Next 10 Years

前言

Kafka在Confulent成立后发展很快,很明显的变化是发布了很多重大的版本。了解未来的规划,对于我们学习和使用kafka有很大的意义。

Kafka Summit 2020 已经在8-24召开,最近抽出时间看了一些视频,由于自己英语是二把刀,因此本文是自己对该主题的自己理解,尽可能还原Gwen Shapira 的分享(Apache Kafka - The Next 10 Years),中文全网唯一

Kafka 设计原则

High Performance from First Principles

Principles in Action: Elasticity

Principles in Action: Scalability

Principles in Action: Operationally Friend

Design Considerations in Action

KIP-405: Kafka Tiered Storage

Kafka数据在流式中通常使用尾部读取。尾部读取利用操作系统的页面缓存来提供数据,而不是磁盘读取。旧数据和故障恢复通常会从磁盘读取,这些通常很少见。

在分层存储方法中,Kafka集群配置了两层存储-本地和远程。本地层与当前的Kafka相同,使用Kafka broker上的本地磁盘存储日志段。新的远程层使用HDFS或S3等系统来存储完整的日志段。对应于每个层定义了两个单独的保留期。启用远程层后,可以将本地层的保留期从几天显着减少到几个小时。远程层的保留期可能会更长,几天甚至几个月。当日志段在本地层上滚动时,它将与相应的偏移量索引一起复制到远程层。延迟敏感的应用程序执行尾部读取,并利用现有的Kafka机制从本地层提供服务,该机制有效地使用页面缓存来提供数据。回填和应用程序从故障层恢复,需要比本地层中的数据更旧的数据从远程层提供服务。

该解决方案允许扩展存储独立于Kafka集群中的内存和CPU的存储,从而使Kafka成为长期存储解决方案。这也减少了在Kafka代理上本地存储的数据量,因此减少了在恢复和重新平衡期间需要复制的数据量。远程层中可用的日志段无需在代理上还原或延迟还原,而是从远程层提供。这样,增加保留期不再需要扩展Kafka群集存储和添加新节点。同时,总体数据保留时间仍然可以更长,从而无需使用单独的数据管道来将数据从Kafka复制到外部存储,就像目前在许多部署中所做的那样。

KIP-500: Replace ZooKeeper with a Self-Managed Metadata Quorum

主要目的是让部署更简单,配置更简单,用log存储元数据。

主要解决的两三个问题:The Controller Quorum和Broker Metadata Management,以及The Broker State Machine

下一代Kafka架构

更插件化,更弹性,更像云服务一样

Elastic

增加,移除brokers更弹性

Integrated

像使用log一样使用kafka,可以很好的和其他系统集成:像S3、HDFS 等所有的存储系统

Infinite

无限存储,你可以增加更多的broker,没有任何限制,并且不会影响性能。

参考

  1. Keynote Day 1 Morning | Kafka Summit 2020

  2. kafka-summit.org/2020-schedule

  3. 分布式系统理论之Quorum机制

  4. Kafka Improvement Proposals

ice-scripts到ice.js实战迁移之路

TrumanDu github stats

为什么要升级?

纬度\版本 icejs 1.x ice-scripts 2.x ice-scripts 1.x
定位 研发框架 构建工具 构建工具
配置文件 build.json ice.config.js package.json(buildConfig)
文档地址 访问 访问 访问
发布时间 2020.02 2019.06 2018.02
可渐进升级性 不好 不好
插件能力 工程+运行时 工程
工程配置
运行时配置 默认支持 默认不支持 默认不支持
SSR 支持 不支持 不支持

如果你看了这个对比还无法决定,那么说一说我迁移的原因:

  1. ice-scripts官方不维护,查找文档较难
  2. 解决技术债
  3. 我想使用一些新的功能,例如:Hooks and Function Components(当然并不是说不升级就不能用)
  4. 新的前端工程方式,我之所以这么命名,因为我不是一个专业的前端开发,无法将自己的注意力集中在前端领域,只好跟着大厂,这样就不会迷路。这次新版本配置的eslint prettier挺有用。
  5. 前端权限的简洁化(之前推荐的是ant deisgn auth真心不好用)
  6. 布局的简介化

Hooks and Function Components扫盲

快速一览

props非必须,两种方式:

1
2
3
4
const Example = (props) => {
// You can use Hooks here!
return <div />;
}
1
2
3
4
function Example(props) {
// You can use Hooks here!
return <div />;
}

使用useState处理函数组件的状态,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { useState } from 'react';

function Example() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

这里要注意的是setCount异步函数,它不会改变count,而是创建一个新的count。而且在接受同一个 state 对象时,即使其对象里的属性变了,但对象地址没变,是不会更新视图的。

函数中的 setXXX 注意事项:

1. 不可局部更新视图

2. 地址一定要变

阅读全文 »

设计模式总结

创建型

创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创
建代码和使⽤代码。

单例模式⽤来创建全局唯⼀的对象。

⼯⼚模式⽤来创建不同但是相关类型的对象(继承同⼀⽗类或者接⼝的⼀组⼦类),由给定的参数来决定创建哪种类型的对象。

建造者模式是⽤来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。

原型模式针对创建成本⽐较⼤的对象,利⽤对已有对象进⾏复制的⽅式进⾏创建,以达到节省创建时间的⽬的。

单例模式

懒汉

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton {
private static volatile LazySingleton instance = null;

private LazySingleton() {
}

public synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

饿汉

1
2
3
4
5
6
7
8
9
10
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();

private HungrySingleton() {
}

public static HungrySingleton getInstance() {
return instance;
}
}

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}

public static StaticInnerClassSingleton getInstance() {
return LazyHolder.innerClassSingleton;
}

private static class LazyHolder {
private static StaticInnerClassSingleton innerClassSingleton = new StaticInnerClassSingleton();
}
}

枚举类

1
2
3
4
5
6
7
public enum SingletonEmum {
INSTANCE;

public void doSomething() {
System.out.println("value");
}
}

工厂模式

工厂方法与抽象工厂的区别是:抽象工厂可以在一个工厂内生产多个商品,工厂方法只能生产一个

主要角色

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

工厂方法模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
interface Factory{
void produce();
}
class CarFactory implements Factory{
@Override
public void produce() {
System.out.println("汽车工程生产");
new Bus();
}
}
class SuperCarFactory implements Factory{
@Override
public void produce() {
System.out.println("超跑汽车工程生产");
new SuperCar();
}
}

interface Car{
void show();
}

class Bus implements Car{
@Override
public void show() {
System.out.println("公共汽车。。。");
}
}
class SuperCar implements Car{
@Override
public void show() {
System.out.println("超跑。。。");
}
}

抽象工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
interface Factory {
Product produceEngine();
Product produceTyre();
}

class CarFactory implements Factory {

@Override
public Product produceEngine() {
System.out.println("汽车工厂:生产发动机");
return new Engine();
}

@Override
public Product produceTyre() {
System.out.println("汽车工厂:生产发轮胎");
return new Tyre();
}
}

class SuperCarFactory implements Factory {

@Override
public Product produceEngine() {
System.out.println("超跑汽车工厂:生产发动机");
return new Engine();
}

@Override
public Product produceTyre() {
System.out.println("超跑汽车工厂:生产发轮胎");
return new Tyre();
}
}

interface Product {
void show();
}

class Tyre implements Product {
@Override
public void show() {
System.out.println("轮胎。。。");
}
}

class Engine implements Product {
@Override
public void show() {
System.out.println("发动机。。。");
}
}

建造者模式

适用场景

参数很多的bean初始化,可以对参数的强约束。

实现

使用静态内部类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Bean {
private String a;
private String b;
private String c;
private String d;

private Bean(Builder builder) {
a = builder.a;
b = builder.b;
c = builder.c;
d = builder.d;
}

static class Builder {
private String a;
private String b;
private String c;
private String d;

public Builder a(String a) {
this.a = a;
return this;
}

public Builder b(String b) {
this.b = b;
return this;
}

public Builder c(String c) {
this.c = c;
return this;
}

public Builder d(String d) {
this.d = d;
return this;
}

public Bean build() {
return new Bean(this);
}
}
}

使用:

1
Bean bean = new Bean.Builder().a("a").b("b").c("c").build();

原型模式

原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆

结构型

代理模式

主要角色

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public interface Subject {
void request();
}
public class ConcreteSubject implements Subject {
@Override
public void request() {
System.out.println("执行请求");
}
}

public class Proxy implements Subject {

private Subject subject = new ConcreteSubject();
public void preRequest() {
System.out.println("pre");
}
@Override
public void request() {
preRequest();
subject.request();
afterRequest();
}
public void afterRequest() {
System.out.println("after");
}
public static void main(String[] args) {
Proxy proxy =new Proxy();
proxy.request();
}
}

桥接模式

定义

桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

适用场景:

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。例如包有钱包,手提包,又有颜色(黄、红、蓝)

实现

主要角色

  • 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
abstract class Bag {
Color color;
public void setColor(Color color) {
this.color = color;
}

abstract void echoName();
}

class HandBag extends Bag {

@Override
void echoName() {
System.out.println(color.getColor() + "的挎包");
}
}
class Wallet extends Bag {

@Override
void echoName() {
System.out.println(color.getColor() + "的钱包");
}
}

interface Color {
String getColor();
}

class Yellow implements Color{

@Override
public String getColor() {
return "黄";
}
}

class Red implements Color{

@Override
public String getColor() {
return "红";
}
}

装饰器模式

定义

装饰(Decorator)模式:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式

jdk io就使用了装饰器模式,例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

1
2
BufferedReader in=new BufferedReader(new FileReader("filename.txtn));
String s=in.readLine();

应用场景

当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。

实现

主要角色

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public interface Component {
void operation();
}
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("Concrete Component operation");
}
}

public class Decorator implements Component{
Component component;

public Decorator(Component component){
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}

public class ConcreteDecoratorA extends Decorator {

public ConcreteDecoratorA(Component component) {
super(component);
}

public void addFunction(){
System.out.println("add a function");
}

@Override
public void operation() {
addFunction();
super.operation();
}
}

public class ConcreteDecoratorB extends Decorator {

public ConcreteDecoratorB(Component component) {
super(component);
}

public void addFunction(){
System.out.println("add b function");
}

@Override
public void operation() {
addFunction();
super.operation();
}
}

public static void main(String[] args) {
Component component = new ConcreteComponent();
Decorator decorator = new ConcreteDecoratorA(component);
decorator.operation();
}

这样做的好处是可以通过组合形成更多的构件功能,例如如上代码,可以组装出具有a功能的ConcreteComponent,具有b功能的ConcreteComponent,或者新增一个NewConcreteComponent。进而可以组装出具有a功能的NewConcreteComponent

适配器模式

定义

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

实现

主要角色

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式

对象适配器模式

门面模式

定义

外观(Facade)模式的定义:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

实现

主要角色

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

组合模式

定义

组合(Composite)模式的定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性

实现

主要角色

  • 抽象构件(Component):它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
  • 树叶构件(Leaf):是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
  • 树枝构件(Composite):是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。


亨元模式

定义

享元(Flyweight)模式的定义:运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的又橡来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

实现

主要角色

  • 抽象享元(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  • 具体享元(Concrete Flyweight):实现抽象享元角色中所规定的接口。
  • 非享元(Unsharable Flyweight):是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  • 享元工厂(Flyweight Factory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

行为型

模板方法

定义

模板方法(Template Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

父类中定义公共方法或者执行步骤,有差异的部分由具体的子类去实现。

适用场景:

  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。

实现

主要角色

  1. 抽象类(Abstract Class)
    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 基本方法
      • 抽象方法:在抽象类中申明,由具体子类实现。
      • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
      • 钩子方法(非必须)在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  2. 具体子类(ConcreteClass):实现抽象类中所定义的抽象方法和钩子方法

实现demo代码

为了避免子类较多,可以考虑匿名内部类,JedisCluster的命令实现就是一个很好的例子。

开源demo案例

JedisClusterCommand的实现即是一个明显的例子。JedisClusterCommand实现了分槽,获取connect,执行命令,重试等公共操作。子类中实现具体的执行命令。

JedisClusterCommand

JedisCluster.set

观察者模式

定义

观察者(Observer)模式: 指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。

实现

主要角色

  • Subject 就是抽象主题:它负责管理所有观察者的引用,同时定义主要的事件操作。
  • ConcreteSubject 具体主题:它实现了抽象主题的所有定义的接口,当自己发生变化时,会通知所有观察者。
  • Observer 观察者:监听主题发生变化相应的操作接口。
  • Concrete Observer 具体观察者,实现抽象观察者中定义的抽象方法

实现demo代码

开源demo案例

命令模式

定义

命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

适合场景

  1. 当系统需要将++请求调用者与请求接收者解耦++时,命令模式使得调用者和接收者不直接交互。
  2. 当系统需要++随机请求命令或经常增加或删除命令++时,命令模式比较方便实现这些功能。
  3. 当系统需要++执行一组操作++时,命令模式可以定义宏命令来实现该功能。
  4. 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。

实现

主要角色

  • Command 命令:命令接口定义一个抽象方法
  • ConcreteCommand:具体命令,负责调用接受者的相应操作
  • Invoker 请求者:负责调用命令对象执行请求
  • Receiver 接受者:负责具体实施和执行一次请求

实现demo代码

开源demo案例

Tomcat 中命令模式在 Connector 和 Container 组件之间有体现,Tomcat 作为一个应用服务器,无疑会接受到很多请求,如何分配和执行这些请求是必须的功能。

Connector和Container两个接口。Connector是抽象命令请求者,Container是抽象命令接收者,server是这一切的缘由,HttpProcessor是抽象命令。

在tomcat中的实现形式是:server需要Connector来接受来自外接的Http请求,然后Connector接受到请求,并创建了命令HttpProcessor,然后server将这个命令交给了Container接收者。

参考

  1. 图说设计模式
  2. 设计模式

语法

特殊声明

_用法

  • 用在 import _ "github.com/go-sql-driver/mysql"

    程序默认执行init方法

  • 用在函数返回值 _, err := client.Do(req)

    忽略相应的返回值

new 函数

内建的new函数也是一种创建变量的方法,new(type)表示创建一个type类型的匿名变量,并初始化为type类型的零值,返回变量的地址,指针类型为*type

1
2
3
4
p := new(int)   	// p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // 0
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // 2

如下函数完成同样的功能:创建变量,返回变量地址

1
2
3
4
5
6
7
func newA() *int {
return new(int)
}
func newB() *int {
var i int
return &i
}

基本类型

bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 的别名

rune // int32 的别名
// 表示一个 Unicode 码点

float32 float64

complex64 complex128
本例展示了几种类型的变量。 同导入语句一样,变量声明也可以“分组”成一个语法块。

命名返回值

没有参数的 return 语句返回已命名的返回值。也就是 直接 返回

1
2
3
4
5
6
7
8
func method() (x, y int) {
x = 1
y = 2
return
}
func main() {
fmt.Println(method()) // 1 2
}

短变量声明

在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。

函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。

指针

指针声明var p *int

指针赋值var p *int = &i or pp := &i

空指针:nil

指针使用:主要经过三个步骤:声明、赋值和访问指针指向的变量的值

1
2
3
4
var i int = 10
var p *int = &i
*p = 12
fmt.Println(p,*p)

结构体

1
2
3
4
5
6
7
8
9
10
type Vertex struct {
X, Y int
}

var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)

数组

数组声明及赋值

两种方式:

1
2
3
4
5
6
// 第一种
var a [2]string
a[0] = "hello"
a[1] = "world"
// 第二种
aa :=[2]int{10,20}

遍历

1
2
3
4
5
6
7
8
   // 第一种
for i, s := range a {
fmt.Printf("%d : %s\n", i, s)
}
// 第二种
for i := 0; i < len(aa); i++ {
fmt.Printf("%d : %d\n", i, aa[i])
}

map

map 声明及赋值

1
2
3
4
5
6
7
8
9
// 第一种
var m = make(map[string]string)
m["a"] = "A"
m["b"] = "B"
// 第二种
var m = map[string]string{
"a":"A",
"b":"B",
}

遍历

1

slice 切片

切片并不存储任何数据,它只是描述了底层数组中的一段。

更改切片的元素会修改其底层数组中对应的元素。

与它共享底层数组的切片都会观测到这些修改。

声明

1
2
3
4
5
6
7
8
9
10
11
a := [5]int{1,2,3,4,5}

// 类型 []T 表示一个元素类型为 T 的切片
// 第一种 不指定具体大小
q := []int{2, 3, 5, 7, 11, 13}
// 第二种,类型自动推断
qq := a[0:2]
// 第三种,声明为切片
var s []int = a[1:4]
// 第四种
aa := make([]int, 5)

用法

切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。

流程控制语句:for、if、else、switch 和 defer

for

1
2
3
4
sum := 0
for i := 0; i < 10; i++ {
sum += i
}

初始化语句和后置语句是可选的

1
2
3
for ; sum < 1000; {
sum += sum
}

for 是 Go 中的 “while”

1
2
3
for sum < 10 {
sum += 1
}

无限循环

1
2
for {
}

if

1
2
3
if  sum==0 {
fmt.Println(sum)
}

同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if 之内

1
2
3
if sum = 1 ; sum==1 {
fmt.Println(sum)
}

defer

defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。 个人猜测用途是关闭资源,处理异常等。

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用

1
2
3
4
5
func main() {
defer fmt.Println("world")

fmt.Println("hello")
}

测试

go 默认有个轻量级测试框架,可以使用go test命令和testing

创建一个文件,文件名以 _test.go结尾,函数名为TestXXX,并且传递参数(t *testing.T)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package morestrings

import "testing"

func TestReverseRunes(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := ReverseRunes(c.in)
if got != c.want {
t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
}
}
}

然后执行命令go test

方法和接口

函数与方法区别

方法在 func 关键字后是接收者而不是函数名

  1. 普通函数
    1
    2
    3
    func function_name([parameter list]) [return_types] {
    函数体
    }
  2. 方法(如 struct 方法)
    1
    2
    3
    func (variable_name variable_data_type) method_name([parameter list]) [return_type]{
    /* 函数体*/
    }

使用区别

函数: function_name() 函数有参数的话,必须保持类型一致,否则编译失败。

方法:p.method_name()其中 p 可以为指针,也可以为值(p 为结构体的值)

方法的接受者可以为指针,也可以为值。例如:

1
2
3
4
5
6
7
8
9
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

v := Vertex{3, 4}
fmt.Println(v.Abs())

p := &Vertex{4, 3}
fmt.Println(p.Abs())

使用指针接收者的原因有二:

  1. 方法能够修改其接收者指向的值。

  2. 这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。

接口

如果一个类型实现了一个接口需要的所有方法,那么该类型就实现了这个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type User struct {
name string
age int
}

type I interface {
Name()
Age()
}

func (u User)Name() {
fmt.Printf("Name:%v\n", u.name)
}

func (u User)Age() {
fmt.Printf("Age:%v\n", u.age)
}

func main() {
var u I = User{"truman",18}
u.Name()
u.Age()
}

类型断言

类型断言 提供了访问接口值底层具体值的方式。t := i.(T)

1
2
3
4
5
6
var a interface{} = 11
s:= a.(int)
fmt.Println(s) //11

b,ok:= a.(string)
fmt.Println(b,ok)// false

并发

使用go f(a)即可新建一个 goroutine

信道

声明一个信道c := make(chan int)

带缓存的信道 c := make(chan int,10)

仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

通过 channel 可以实现唤醒线程,例如:

1
2
3
4
5
6
7
8
go func() {
data := "hello value:stop"
fmt.Println(data)
time.Sleep(1000000000 * 10)
ch <- data
}()
<-ch
fmt.Println("execute next business.")

range/close

只有发送者才能关闭信道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ch := make(chan string)
go func() {
for i:= range ch {
if i=="hello value:5" {
time.Sleep(1000000000*10)
}
fmt.Println(i)
}
}()

for i := 0; i < 10; i++ {
data := "hello value:"+fmt.Sprintf("%d", i)
ch <- data
}
close(ch)

sync.Mutex

互斥锁

实现从 0 加到 1000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Account struct {
money int
mux sync.Mutex
}

func (a *Account)Inc() {
a.mux.Lock()
a.money++
fmt.Println(a.money)
defer a.mux.Unlock()
}

func main() {
a:=Account{money: 0}
for i := 0; i <1000; i++ {
go a.Inc()
}
time.Sleep(time.Second*10)
fmt.Printf("acount money:%d\n", a.money)
}

学习资源

  1. https://tour.go-zh.org/
  2. How to Write Go Code
  3. Go 語言聖經(中文版)

参考

  1. https://golang.org/doc/code.html#Testing
0%