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

开源项目KafkaCenter 版本持续集成(CI)实践

开篇

本文简单介绍开源项目KafkaCenter 版本持续集成(CI)实践方案,主要解决了三个问题:

  1. 前后端项目编译
  2. 发布Github release包
  3. 制作docker镜像

希望能给你带来一点参考。

详细信息可以参考 https://github.com/xaecbd/KafkaCenter

正文

版本管理

KafkaCenter 后端服务是java,使用maven管理的,有多个module,为了做到版本一致,我们使用了${revision}。这个是maven3.5+ 才支持,主要是为了对CI友好。

例如:

父pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<project >
<modelVersion>4.0.0</modelVersion>
<groupId>org.nesc.ec.bigdata</groupId>
<artifactId>KafkaCenter</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<name>KafkaCenter</name>
<url>https://github.com/xaecbd/KafkaCenter</url>
<description>Kafka Center Platform</description>
...
<properties>
<revision>1.0.0-SNAPSHOT</revision>
</properties>

<modules>
<module>KafkaCenter-Base</module>
<module>KafkaCenter-Core</module>
</modules>
</project>

子module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nesc.ec.bigdata</groupId>
<artifactId>KafkaCenter</artifactId>
<version>${revision}</version>
</parent>
<artifactId>KafkaCenter-Base</artifactId>
<name>KafkaCenter-Base</name>
<url>https://github.com/xaecbd/KafkaCenter</url>

</project>

通过如下命令,可以在打包的时候指定版本号:

1
mvn -Drevision=2.1.0 -Dchangelist= clean package

Docker镜像

在项目根目录下新建docker文件夹,包含三个文件:

docker-build-release.sh build镜像及发布镜像的脚本

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env bash

DOCKER_IMAGE_NAME="xaecbd/kafka-center"
VERSION=${TRAVIS_TAG#v}
echo "KafkaCenter version: $VERSION"
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
cp $TRAVIS_BUILD_DIR/KafkaCenter-Core/target/*.jar $TRAVIS_BUILD_DIR/docker/
docker build -t $DOCKER_IMAGE_NAME:$VERSION $TRAVIS_BUILD_DIR/docker/
docker images
docker push $DOCKER_IMAGE_NAME:$VERSION

Dockerfile docker 镜像定义文件

1
2
3
4
5
6
7
8
9
10
FROM adoptopenjdk/openjdk8:jre8u252-b09-alpine
LABEL author="Turman.P.Du"
ENV PROJECT_BASE_DIR /opt/app/kafka-center/
WORKDIR ${PROJECT_BASE_DIR}

COPY *.jar ${PROJECT_BASE_DIR}/
COPY *.sh ${PROJECT_BASE_DIR}/

RUN chmod +x *.sh
ENTRYPOINT ["sh","start.sh"]

start.sh 应用启动脚本,非必须,只是我们习惯放这么个脚本,可以在应用启动前做一些工作。推荐在启动java应用前增加exec命令,这样可以让spring容器在docker容器停止运行前执行一些操作(可以用作应用停止前执行收尾工作,例如保存数据,停止不可中断的任务)。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
echo "PROJECT_BASE_DIR :"$PROJECT_BASE_DIR
#cd $APP_ROOT_DIR
cd $PROJECT_BASE_DIR

appName=`ls|grep .jar$`
echo start to run $appName

if [ -n "$JAVA_OPTIONS" ];then
exec java $JAVA_OPTIONS -jar $appName $@
else
exec java -jar $appName $@
fi

travis

github action已经很好了,我没有采用的原因是需要熟悉成本有些操作可能暂时做不到。而可以预见性的travis都能很好的做到。因此目前的方案是采用travis。

实现要求

通过新建tag(只能是tag触发),自动编译前后端代码,发布github release,构建docker镜像,发布镜像。

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
language: java
jdk:
- openjdk8

cache:
directories:
- $HOME/.m2
- $HOME/.npm
- $TRAVIS_BUILD_DIR/

dist: trusty
jobs:
include:
- stage: ui_build
language: node_js
node_js: 10.15.2
script: cd KafkaCenter-Frontend && npm install && npm run build

- stage: GitHubRelease
install:
- echo GitHubRelease
script: mvn -Drevision=${TRAVIS_TAG#v} clean package -Dmaven.test.skip=true
before_deploy:
- mkdir -p $TRAVIS_BUILD_DIR/deploy
- cp $TRAVIS_BUILD_DIR/KafkaCenter-Core/target/*.gz $TRAVIS_BUILD_DIR/deploy
- rm -f $TRAVIS_BUILD_DIR/KafkaCenter-Core/target/*.gz
- ls -l $TRAVIS_BUILD_DIR/deploy
deploy:
provider: releases
api_key: $API_KEY
file_glob: true
skip_cleanup: true
file: deploy/*.tar.gz
on:
tags: true
after_deploy: rm -rf $TRAVIS_BUILD_DIR/deploy

- stage: BuildDockerImageforRelease
install:
- echo Build Docker Image for Release
before_script:
- chmod +x ./docker/docker-build-release.sh
script: ./docker/docker-build-release.sh

stages:
- name: ui_build
if: tag =~ /^v\d+\.\d+\.\d+.*$/
- name: GitHubRelease
if: tag =~ /^v\d+\.\d+\.\d+.*$/
- name: BuildDockerImageforRelease
if: tag =~ /^v\d+\.\d+\.\d+.*$/

notifications:
email: true

在travis管理页面需要配置API_KEYDOCKER_USERNAMEDOCKER_PASSWORD

参考

  1. Maven CI Friendly Versions
  2. spring-boot-docker
  3. docs.travis-ci.com

基于spring security oauth2 client最佳实践

开篇语

最近很少写文章,一个是确实是很忙,另外一个原因是没有什么深度的技术文章可写。之前写blog的原因是为了技术存档,便于自己某天需要的时候再去看看,另外是总结一下。这段时间不太想写种水文,这篇文章同样不是什么深度性的文章,不过确实困扰了我超过3天时间,网络上很多文章都没能解决我的问题,基本上大家是介绍整个oauth,体系很大,文章却写的不全,要么就是方案很复杂(有点追求,不想采用),对我几乎无帮助。

按说官网文档应该够全了,但是对于一个不熟悉spring security的人,想要快速入手,还是很难,文档我就看了很久也没有找到自己想要的。官方的demo局限于github,google。我想实现的是自定义的oauth2登录。

当我解决了以后,发现别的小伙伴也有类似的疑惑。索性就写下来,只是技巧,写最少的代码,最优雅的完成自己想要的功能。本篇文章不讲解oauth认证基本知识。

实践

1.引入相应的依赖包

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

2.参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring.security.oauth2.client.provider.customer.authorization-uri=http://xxxxxxxx/oauth2/v1/authorize
spring.security.oauth2.client.provider.customer.token-uri=http://xxxxxxxx/oauth2/v1/token
spring.security.oauth2.client.provider.customer.user-info-uri=http://xxxxxxxx/oauth2/v1/user-info
spring.security.oauth2.client.provider.customer.user-info-authentication-method=header
spring.security.oauth2.client.provider.customer.user-name-attribute=name

spring.security.oauth2.client.registration.app.client-id=xxxxxxxxxxx
spring.security.oauth2.client.registration.app.client-secret=xxxxxxxxxxx
spring.security.oauth2.client.registration.app.client-name=Client for user scope
spring.security.oauth2.client.registration.app.provider=customer
spring.security.oauth2.client.registration.app.scope=user
spring.security.oauth2.client.registration.app.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.app.client-authentication-method=basic
spring.security.oauth2.client.registration.app.authorization-grant-type=authorization_code

3.代码实现

在页面登录按钮,添加跳转地址/oauth2/authorization/app 这个是默认的地址,可以通过配置文件修改

新建Oauth2LoginSecurityConfig 实现如下功能既可

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
@Configuration
public class Oauth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests(a -> a
.antMatchers("/**").permitAll().anyRequest().authenticated()
)
.exceptionHandling(e -> e
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
)
.formLogin()
.loginPage("/#/user/login")
.permitAll()
.and()
.logout().permitAll()
.and()
.oauth2Login().userInfoEndpoint().and().successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
Map<String, Object> attributes = oAuth2User.getAttributes();
// ....登录成功以后,既可获取用户信息

// ..跳转到成功页面
httpServletResponse.sendRedirect("/#/home");
}
});
}

}

上面核心代码为.oauth2Login().userInfoEndpoint().and().successHandler() 这个完成获取code,根据code获取token,根据token获取user信息。熟悉oauth2 code的认证流程,应该就能明白。希望能给你带来一点点帮助。

后话

上面没有什么说的,不过最近越来越发现在职场上需要一种能力,快速学习的能力,对未知事物有非方向上认知错误。也就是说当我们对一个技术架构,或者框架不熟的情况下,一些基本技术常识往往就发挥很大的作用。在开发的路上少追求技巧性的东西,多追究一些理论和本质。这样对快速学习一个技术会有很大的帮助。

前言

Spring Boot应用监控有很多方案,例如elastic APM,Prometheus等。各有特色,本次实践采用方案:Micrometer+Prometheus+Grafana

选择Micrometer最重要的原因是他的设计很灵活,并且和spring boot 2.x集成度很高。对于jvm的监控很容易集成,难度很小。本次实践包含jvm监控和业务性能指标监控。

环境准备

  1. 搭建promethues

    1
    2
    3
    4
    5
    docker run \
    -p 9090:9090 \
    --name prometheus
    -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \
    prom/prometheus
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    global:
    scrape_interval: 15s # By default, scrape targets every 15 seconds.
    evaluation_interval: 15s # By default, scrape targets every 15 seconds.
    # scrape_timeout is set to the global default (10s).
    # Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
    rule_files:
    # - "first.rules"
    # - "second.rules"

    # A scrape configuration containing exactly one endpoint to scrape:
    # Here it's Prometheus itself.
    scrape_configs:
    # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
    - job_name: 'demo_platform'

    # Override the global default and scrape targets from this job every 5 seconds.
    scrape_interval: 5s

    metrics_path: '/actuator/prometheus'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['127.0.0.1:8080']
  2. 搭建grafana

    1
    docker run -d -p 3000:3000 --name grafana grafana/grafana:6.5.0

Micrometer简介

Micrometer(译:千分尺) Micrometer provides a simple facade over the instrumentation clients for the most popular monitoring systems. 翻译过来大概就它提供一个门面,类似SLF4j。支持将数据写入到很多监控系统,不过我谷歌下来,很多都是后端接入的是Prometheus.

Micrometer提供了与供应商无关的接口,包括 timers(计时器)gauges(量规)counters(计数器)distribution summaries(分布式摘要)long task timers(长任务定时器)。它具有维度数据模型,当与维度监视系统结合使用时,可以高效地访问特定的命名度量,并能够跨维度深入研究。

支持的监控系统:AppOptics , Azure Monitor , Netflix Atlas , CloudWatch , Datadog , Dynatrace , Elastic , Ganglia , Graphite , Humio , Influx/Telegraf , JMX , KairosDB , New Relic , Prometheus , SignalFx , Google Stackdriver , StatsD , Wavefront

Micrometer提供的度量类库

Meter是指一组用于收集应用中的度量数据的接口,Meter单词可以翻译为”米”或者”千分尺”,但是显然听起来都不是很合理,因此下文直接叫Meter,理解它为度量接口即可。Meter是由MeterRegistry创建和保存的,可以理解MeterRegistryMeter的工厂和缓存中心,一般而言每个JVM应用在使用Micrometer的时候必须创建一个MeterRegistry的具体实现。Micrometer中,Meter的具体类型包括:TimerCounterGaugeDistributionSummaryLongTaskTimerFunctionCounterFunctionTimerTimeGauge。一个Meter具体类型需要通过名字和Tag(这里指的是Micrometer提供的Tag接口)作为它的唯一标识,这样做的好处是可以使用名字进行标记,通过不同的Tag去区分多种维度进行数据统计。

Spring Boot集成

与spring boot 集成,这里的metric主要是由spring actuator 提供

安装

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
management:
endpoint:
health:
enabled: false
endpoints:
web:
exposure:
include: '*'
exclude: env,beans
metrics:
enable:
http: false
hikaricp: false

这里有几个注意的点management.endpoint.health.enabled只是为了禁用spring 默认的健康检查,非必须。exclude: env,beans也不需要配置,只是在我项目中为了减少导出的metric。同理management.metrics.enable也是为了减少收集的数据,使用方法为你定义指标的前缀。

只有management.endpoints.web.exposure.include为必须的,这里也只是为了导出/actuator/prometheus,通过该地址可以访问到响应的metric信息。

可视化

访问 http://localhost:8080/actuator/prometheus 即可看到响应的metric信息。

在grafana中中导入JVM (Micrometer)

即可看到如下效果:

自定义业务性能监控

因为系统遗留监控代码的原因,这里采用的是全局静态方法实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected static Iterable<Tag> tags(String service, String category, String method) {
return Tags.of("service", service, "category", category, "method", method);
}

protected static Iterable<Tag> tags(String service, String category) {
return Tags.of("service", service, "category", category);
}

public static void controllerMetric(String service, MonitorMetric.MonitorOperationType type, String method, long time) {
try {
Metrics.counter(Constants.HTTP_REQUESTS_TOTAL, tags(service, type.name(), method)).increment();
Metrics.timer(Constants.REQUESTS_LATENCY, tags(service, type.name())).record(Duration.ofMillis(time));
} catch (Exception e) {
e.printStackTrace();
}
}

解释一下,这里可以统计出请求数和请求延迟。

对于每秒请求数据量,可以使用increase(http_requests_total{job=~"$job",instance=~"$instance"}[1m])

对于平均请求延迟,可以使用rate(timer_sum[1m])/rate(timer_count[1m])

对于Throughput 可以使用rate(timer_count[1m])

使用中的困惑

问题

Percentile histogramsDistribution summaries性能损失还无法确定,不过查看PrometheusTimer,结合测试,还是有一定的性能损失,不过这里未深入研究。

全局使用一些开发建议

可以在定义静态方法类,初始化的时候做一点配置,registry可以使用spring 注入进来例如:

1
2
@Autowired 
MeterRegistry registry;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public MonitorMetric(MeterRegistry registry) {
registry.config().meterFilter(
new MeterFilter() {
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
if (id.getName().startsWith("requests_latency")) {
return DistributionStatisticConfig.builder()
.percentiles(0.5, 0.75, 0.9)
.sla(1)
.expiry(Duration.ofMinutes(1))
.minimumExpectedValue(1L)
.build()
.merge(config);
}
return config;
}
});
Metrics.addRegistry(registry);
}

参考

  1. 使用 Micrometer 记录 Java 应用性能指标
  2. Micrometer 快速入门
  3. JVM应用度量框架Micrometer实战
  4. Micrometer Prometheus

一站式Kafka平台KafkaCenter-开源啦

Important: https://github.com/xaecbd/KafkaCenter

前言

经过一年的不断打磨,在团队成员的共同努力下,终于能以真实的面貌呈现在大家的面前,很开心,很激动。开源软件,只是为了和大家交个朋友,喜欢的话,star,star,star,重要的事情说三遍!

之前做过Kafka 平台化的一点经验分享,以至于很多小伙伴问了,这个东西有没有开源,在团队成员的共同努力下,欢迎感兴趣的同学加入我们,做点感兴趣的事。

KafkaCenter是什么?

KafkaCenter是Kafka 集群管理和维护,生产/消费监控,生态组件使用的统一一站式平台。

KafkaCenter 解决了什么问题

在给大家说我们解决什么问题之前,先说说在没有KafkaCenter之前我们的面临的问题。

我们面临的问题

  • 创建topic,人工处理化
  • 相关kafka运维,监控孤岛化
  • 现有消费监控工具监控不准确
  • 无法拿到Kafka 集群的summay信息
  • 无法快速知晓集群健康状态
  • 无法知晓业务对team kafka使用情况
  • kafka管理,监控工具稀少,没有一个好的工具我们直接可以使用
  • 无法快速查询topic消息

Kafka Center解决了哪些问题

  • 统一: 一个平台,一站式包含自助,管理,监控,运维,使用一体化。
  • 流程化: 创建topic流程化,做到对topic使用全生命周期管理。
  • 复用: 平台支持接入多个集群,复用性很高。
  • 成本: 只用部署一套程序,节省机器资源。降低运维成本,高效运维。
  • 生态: 目前已经接入connect,ksql。
  • 便捷: 提供便捷工具,让无需有kafka使用经验的人,都可以方便生产、消费消息。
  • 全局: 可以站在不同的维度查看目前kafka使用情况
  • 权限: 完善的权限设计,减少风险漏洞。

功能模块介绍

  • Home->
    查看平台管理的Kafka Cluster集群信息及监控信息
  • Topic->
    用户可以在此模块查看自己的Topic,发起申请新建Topic,同时可以对Topic进行生产消费测试。
  • Monitor->
    用户可以在此模块中可以查看Topic的生产以及消费情况,同时可以针对消费延迟情况设置预警信息。
  • Kafka Connect->
    实现用户快速创建自己的Connect Job,并对自己的Connect进行维护。
  • KSQL->
    实现用户快速创建自己的KSQL Job,并对自己的Job进行维护。
  • Approve->
    此模块主要用于当普通用户申请创建Topic,管理员进行审批操作。
  • Setting->
    此模块主要功能为管理员维护User、Team以及kafka cluster信息
  • Kafka Manager->
    此模块用于管理员对集群的正常维护操作。

说了这么多,还是给大家看看主要系统截图吧!








成为一个更好的架构师

本文翻译于SoftwareArchitect,原创翻译,有删减,介意请查看原文,转载请联系我。

扫码关注我

多年前,有人问我:”如何成为软件架构师?”。我认为需要必要的技能,经验以及积累知识所需的时间和奉献精神。

1. 内容

  • 软件架构师的定义
  • 软件架构的级别
  • 软件架构师的常规工作内容
  • 软件架构师的重要技能
  • 架构师技术路线图

2. 软件架构师的定义

在开始之前,让我们先看看这个定义。

  • 软件架构师是一位软件专家,他可以进行高层设计选择并决定技术标准,包括软件编码标准,工具和平台。首席专家被称为首席架构师。(来源:Wikipedia: Software Architect)

3. 软件架构的级别

架构可以被抽象成很多个级别,不同的人会有不同的分发,这里我更喜欢分成三个级别:

  • 应用程序级别:最低的体系结构级别。专注于一个单一的应用程序。非常详细的底层设计。通常在一个开发团队中进行沟通
  • 解决方案级别:中级架构。专注于满足业务需求(业务解决方案)的一个或多个应用程序。有一些高层次,但主要是低层次的设计。多个开发团队之间的沟通。
  • 企业级别:最高级别的体系结构。专注于多种解决方案。高层次的抽象设计,需要解决方案或应用程序架构师对其进行详细说明。整个组织的沟通,更多详情见链接

4. 软件架构师的常规工作内容

要了解架构师所需的必要技能,我们首先需要了解常规工作内容。我认为以下(非最终)清单包含最重要的工作内容:

  • 定义和决定开发技术和平台
  • 定义开发标准,例如编码标准,工具,审查流程,测试方法等。
  • 支持识别和理解业务需求
  • 设计系统并根据需求做出决策
  • 记录并传达架构定义,设计和决策
  • 检查并回顾架构和代码,例如,检查定义的模式和编码标准是否正确实施
  • 与其他架构师和利益相关者合作
  • 指导并咨询开发人员
  • 将高级设计改进为低级设计并详细说明。注意:架构是一项连续的工作,尤其是在将其应用于敏捷软件开发中时。因此,这些工作一遍又一遍地进行。

5. 软件架构师的重要技能

为了支撑常规工作内容,需要特定技能。根据我的经验,阅读书籍和讨论,我们可以将其归结为每个软件架构师应具备的以下 10 种技能:

1
设计,决定,简化,编码,文档,沟通,估计,平衡,咨询,市场

(1)设计

什么是好的设计?这可能是最重要和最具挑战性的问题。我将在理论和实践之间进行区分。以我的经验,两者的结合是最有价值的。让我们从理论开始:

  • 了解基本的设计模式:模式是架构师开发可维护系统所需的最重要工具之一。使用模式,您可以重复使用设计,以通过可靠的解决方案解决常见问题。由 GoF 编写的《设计模式:可重用的面向对象软件的要素》一书是所有从事软件开发的人必读的书。尽管该模式已发布 20 多年,但它们仍然是现代软件体系结构的基础。例如,本书描述了模型-视图-控制器(MVC)模式,该模式在许多领域都得到了应用,或者是更新模式的基础,例如 MVVM。

  • 深入研究模式和反模式:如果您已经了解所有基本的 GoF 模式,则可以使用更多的软件设计模式来扩展您的知识。或更深入地研究您感兴趣的领域。我最喜欢的应用程序集成之一是 Gregor Hohpe 撰写的“ Enterprise Integration Patterns”一书。无论两个应用程序需要交换数据,无论是来自某些旧系统的旧式文件交换还是现代微服务体系结构的交换,这本书都适用于各个领域。

  • 了解质量度量:定义架构不是终点。它有定义,应用和控制指南和编码标准的原因。您出于质量和非功能性要求而这样做。您想要一个可维护,可靠,适应性强,安全,可测试,可伸缩,可用等的系统。要实现所有这些质量属性,一件事情就是应用良好的架构工作。您可以开始更多地了解维基百科上的质量度量。理论很重要。如果您不想成为象牙塔架构师,那么实践同样重要,甚至更为重要。

  • 尝试并了解不同的技术堆栈:如果您想成为一名更好的架构师,我认为这是最重要的活动。试用(新)技术堆栈,并了解它们的兴衰。不同或新技术具有不同的设计方面和模式。您很可能从翻阅抽象幻灯片中不会学到任何东西,而是自己尝试一下,并感到痛苦或缓解。架构师不仅应该具有广泛的知识,而且在某些领域还应具有深厚的知识。掌握所有技术堆栈并不重要,但要对您所在地区的最重要知识有深入的了解。另外,请尝试不使用您所处领域的技术,例如,如果您深入 SAP R / 3,则还应该尝试 JavaScript,反之亦然。尽管如此,双方仍会对 SAP S / 4 Hana 的最新进展感到惊讶。例如,您可以自己尝试,然后免费在 openSAP 上课程。好奇并尝试新事物。还可以尝试一些您几年前不喜欢的东西。

  • 分析和理解应用模式:查看任何当前框架,例如 Angular。您可以在实践中研究很多模式,例如“可观察物”。尝试了解它如何在框架中应用,为什么要这样做。而且,如果您真的很专心,请更深入地研究代码并了解其实现方式。

  • 充满好奇并作为一个用户

(2)决定

架构师需要能够做出决定并指导项目或整个组织朝正确的方向发展。

  • 知道什么是重要的:不要浪费时间进行无关紧要的决定或活动。了解重要的事情。据我所知,没有一本书包含这些信息(如果您知道的话,请告诉我)。我个人最喜欢的是这两个特征,我通常会在评估某些重要事项时考虑这些特征:
    1. 概念完整性:如果您决定以一种方式来做,那就坚持下去,即使有时最好以其他方式去做。通常,这会导致更简单的总体概念,简化可理解性并简化维护
    2. 一致性:例如你定义了应用的命名,不用关心是大写还是小写,但是要做到所有的地方都是一个标准,一个表达方式。
  • 优先排序:某些决定至关重要。如果不及早采取措施,就会需要很多的解决方法,这些措施通常不太可能在以后删除,并且是维护的噩梦,或者更糟的是,开发人员只能停止工作,直到做出决定。在这种情况下,有时最好做出“错误”的决定而不是没有做决定。但是在遇到这种情况之前,请考虑优先考虑即将做出的决定。有不同的方法可以这样做。我建议看一下在敏捷软件开发中广泛使用的加权最短作业优先(WSJF)模型。特别是时间紧迫性和风险降低措施对于评估架构决策的优先级至关重要。
  • 了解自己的能力:不要决定能力之外的事情。这很关键,因为如果不考虑的话,它可能会严重破坏您作为架构师的地位。为避免这种情况,请与您的同伴明确您要承担的责任以及角色的一部分。如果架构师不止一个,那么您应该尊重当前部署的架构级别。作为较低级别的架构师,您最好提出有关高层架构的建议,而不是决策。此外,我建议始终与同伴一起检查关键决策。
  • 评估多个选项:涉及决策时,请始终布置多个选项。在我参与的大多数情况下,都有不止一种可能的(好的)选择。仅选择一个选项在两个方面都是不利的:(1)您似乎没有正确地完成工作,(2)阻碍了做出正确的决定。通过定义度量,可以基于事实而不是直觉来比较选项,例如许可费用或期限。这通常会导致更好和更可持续的决策。此外,可以轻松地将决策出售给不同的利益相关者。此外,如果您没有正确评估选项,则在讨论中可能会遗漏参数。

(3)简化

请记住解决问题的原则 Occam’s Razor(它更偏爱简单)。 我将原理解释为:如果您对问题的假设太多而无法解决,则可能会出错或导致不必要的复杂解决方案。 应该减少(简化)假设以得出好的解决方案。

  • 摇动解决方案:为了简化解决方案,通常有助于“摇动”解决方案并从不同位置查看它们。尝试通过自上而下和自下而上的方式来塑造解决方案。如果您有数据流或流程,请首先从左到右,再从右到左思考。提出以下问题:“在完美的世界中您的解决方案会发生什么?”或:“ X 公司/人会做什么?”(X 可能不是您的竞争对手,而是 GAFA 公司之一。)这两个问题都迫使您减少 Occam’s Razor 建议的假设。
  • 退后一步:经过长时间的深入讨论,通常会得出高度复杂的涂鸦。您永远都不应将这些视为最终结果。退后一步:再次查看全局(抽象级别)。还是有意义吗?然后再次在抽象级别上进行遍历并进行重构。有时,它有助于停止讨论并在第二天继续。至少我的大脑需要一些时间来处理并提出更好,更优雅,更简单的解决方案。
  • 分而治之:通过将问题分成更小的部分来简化问题。然后独立解决它们。然后验证小块是否匹配。退后一步以查看总体情况。
  • 重构不是邪恶的:如果找不到更好的主意,那么从更复杂的解决方案开始是完全可以的。如果解决方案遇到麻烦,您可以稍后重新考虑解决方案并应用您的学习。重构不是邪恶的。但是在开始重构之前,请记住要进行以下工作:(1)进行足够的自动化测试,以确保系统的正确功能;(2)从利益相关者获取支持。要了解有关重构的更多信息,建议阅读“Refactoring. Improving the Design of Existing Code”,作者是 Martin Fowler。

(4)编码

即使作为企业架构师(最抽象的体系结构级别),您仍然应该知道开发人员的日常工作。而且,如果您不了解如何完成此操作,则可能会遇到两个主要问题:

  1. 开发人员不会接受您的说法。
  2. 您不了解开发人员的挑战和需求。
  • 有一个附带项目:此项目的目的是尝试新技术和工具,以了解当今和未来的开发方式。经验是观察,情感和假设的结合(Kurt Schneider 的“软件工程中的经验和知识管理”)。阅读教程或一些利弊是好的。但这仅仅是“书籍知识”。仅当您自己尝试事物时,您才能体验到情绪并建立关于事物好坏的假设。而且,您使用某项技术的时间越长,您的假设就会越好。这将帮助您在日常工作中做出更好的决定。当我开始编程时,我没有代码完成,只有一些实用程序库可以加快开发速度。显然,在这种背景下,我今天会做出错误的决定。今天,我们拥有大量的编程语言,框架,工具,过程和实践。只有您对主要趋势有一定的经验和粗略的了解,才能参与对话并引导开发朝正确的方向发展。
  • 找到正确的事物进行尝试:您无法尝试所有事物。这根本是不可能的。您需要一种更有条理的方法。我最近发现的一种来源是 ThoughtWorks 的 Technology Radar。他们将技术,工具,平台,语言和框架分为四类:采用,试用,评估和保留。 “采用”表示“强烈准备为企业使用做好准备”,“试用”表示“企业应该在一个可以处理风险的项目中进行尝试”,“评估”表示“研究它如何影响您的企业”,“持有”表示“谨慎处理”。通过这种分类,更容易获得新事物的概述及其准备情况,以更好地评估下一步要探索的趋势。

(5)文档

架构文档有时或多或少地很重要。 重要文档是例如体系结构决策或代码准则。 编码开始之前通常需要初始文档,并且需要不断完善。 其他文档可以自动生成,因为代码也可以是文档,例如 UML 类图。

  • 干净的代码:如果操作正确,代码是最好的文档。一个好的架构师应该能够区分好的代码和坏的代码。罗伯特·C·马丁(Robert C. Martin)所著的“清洁代码”一书是了解更多关于好坏代码的宝贵资源。
  • 在可能的情况下生成文档:系统日新月异,很难更新文档。无论是关于 API 还是以 CMDB 形式出现的系统格局:基础信息经常变化太快而无法手动更新相应的文档。示例:对于 API,如果您是模型驱动的,则可以基于定义文件自动生成文档,也可以直接从源代码生成文档。为此,存在许多工具,我认为 Swagger 和 RAML 是学习更多内容的一个很好的起点。
  • 尽可能多地,尽可能少地进行:无论您需要记录什么文档(例如决策文件),都一次尝试仅关注一件事,并且仅包含关于这件事的必要信息。大量的文档很难阅读和理解。附加信息应存储在附录中。特别是对于决策文件,讲一个有说服力的故事而不是仅仅发表大量论据,更为重要。此外,这为您和您的同事节省了很多时间,而后者需要阅读。看看您过去做过的一些文档(源代码,模型,决策文件等),然后问自己以下问题:“是否包含所有必要的信息才能理解它?”,“确实需要哪些信息,并且可以省略吗?”和“文档中是否有红线?”。
  • 了解有关架构框架的更多信息:该点也可以应用于所有其他“技术”点。我把它放在这里,是因为 TOGAF 或 Zachmann 之类的框架正在提供“工具”,这些工具在文档站点上感觉很沉重,尽管它们的附加值并不限于文档。在这样的框架中获得认证可以教会您更系统地解决体系结构。

(6)沟通

根据我的观察,这是最被低估的技能之一。如果您的设计精湛,却无法传达您的想法,那么您的想法可能会受到较小的影响,甚至无法成功。

  • 了解如何交表达您的想法:在董事会或活动挂图上进行协作时,必须了解如何正确使用它,以构筑您和您的同行的思想。我发现《 UZMO-用笔思考》是提高我在这一领域技能的好资源。作为架构师,您通常不仅会参加会议,而且通常需要主持会议并主持会议。
  • 与大群人进行演讲:向小群或大群人展示您的想法对您来说应该是可行的。如果您对此感到不舒服,请开始向您最好的朋友介绍。慢慢扩大小组。这是您只能通过做和离开自己的舒适区来学习的东西。请耐心等待,此过程可能需要一些时间。
  • 找到正确的沟通水平:不同的利益相关者有不同的兴趣和看法。需要在其级别上对它们进行单独处理。在进行交流之前,请退后一步并检查您要共享的信息是否具有正确的级别,有关抽象性,内容,目标,动机等。示例:开发人员通常对解决方案的很少细节感兴趣,而经理则对此感兴趣。宁愿知道哪个选项可以节省最多的钱。
  • 经常交流:如果没有人知道,一个出色的架构将毫无价值。定期并在每个(组织)级别上分发目标体系结构及其背后的思想。安排与开发人员,建筑师和管理人员的会议,以向他们展示所需或已定义的方式。
  • 保持透明:定期交流只能部分缓解缺少的透明度。您需要使决策背后的原因透明化。特别是,如果人们不参与决策过程,则很难理解和遵循其背后的决策和理由。
  • 随时准备做一个演讲:总是会有人提出问题,而您想立即给出正确的答案。尝试始终将最重要的幻灯片放在一个可以显示和解释的合并集中。它为您节省了大量时间,并为您提供安全保护。

(7)估计

  • 了解基本的项目管理原则:作为架构师或首席开发人员,经常会要求您提供估计以实现您的想法:多长时间,花费多少,多少人,哪些技能等?当然,如果您打算引入新的工具或框架,则需要为此类“管理”问题提供答案。最初,您应该能够进行粗略的估算,例如几天,几个月或几年。并且不要忘记,这不仅涉及实现,还有更多活动需要考虑,例如需求工程,测试和修复错误。因此,您应该了解所使用的软件开发过程的活动。您可以应用以获得更好的估计的一件事是使用过去的数据并从中得出您的预测。如果您没有过去的数据,也可以尝试使用 Barry W. Boehm 的 COCOMO 之类的方法。如果您部署在敏捷项目中,请学习如何进行估算和正确计划:Mike Cohn 撰写的《敏捷估算和规划》一书对此领域提供了扎实的概述。
  • 评估“未知”架构:作为架构师,您还应该能够评估架构在当前或将来上下文中的适用性。这不是一件容易的事,但是您可以通过准备一系列常见于每种架构的问题来为它做准备。它不仅涉及架构,还涉及系统的管理方式,因为这也使您了解质量。我建议始终准备一些问题并准备使用。一些一般性问题的想法:
    1. 设计实践:体系结构遵循哪些模式?因此,它们是否正确使用?设计遵循红线还是增长不受控制?是否有清晰的结构和关注点分离?
    2. 开发实践:制定并遵循了代码准则?代码如何版本化?部署实践?
    3. 质量保证:测试自动化范围?静态代码分析到位并取得良好结果?同行评论到位?
    4. 安全性:有哪些安全概念?内置安全性?渗透测试或自动安全分析工具是否到位并经常使用?

(8)平衡

  • 质量是有代价的:前面我谈到了质量和非功能性需求。如果您过度使用体系结构,则会增加成本并可能降低开发速度。您需要平衡架构和功能需求。应避免过度设计。
  • 解决矛盾的目标:矛盾的目标的一个典型示例是短期和长期目标。项目通常倾向于构建最简单的解决方案,而架构师则具有长远的眼光。通常,简单的解决方案不适合长期解决方案,并且有被以后抛弃的风险(降低成本)。为了避免实现错误的方向,需要考虑两点:
    1. 开发人员和企业需要了解长期愿景及其收益,以适应其解决方案
    2. 负责预算的经理需要参与以了解财务影响。不必直接将 100%的远景图放置在适当的位置,但是发达的图块应该适合其中。
  • 冲突管理:架构师通常是具有不同背景的多个小组之间的粘合剂。这可能会导致不同级别的通信发生冲突。为了找到一个能够反映长期战略目标的平衡解决方案,通常架构师的作用就是帮助克服冲突。关于传播理论的起点是舒尔茨·冯·图恩的“四耳模型”。基于此模型,可以显示并推论很多。但是,该理论需要一些实践,在交流研讨会上应该有经验。

(9)咨询

在咨询和辅导方面,积极主动可能是您最好的选择。 如果询问您,通常为时已晚。 而您想要避免在项目现场上进行清理。 您需要以某种方式预见接下来的几周,几个月甚至几年的时间,并为下一步做好准备。

  • 有远见:如果您部署一个项目,无论是传统的瀑布式方法还是敏捷方法,您始终需要对要实现的中长期目标有一个远见。这不是一个详细的概念,而是一个通往每个人都可以工作的路线图。由于您无法一次完成所有工作(这是一段旅程),因此我更喜欢使用成熟度模型。它们给出了易于使用的清晰结构,并且每次都给出了当前的进度状态。对于不同的方面,我使用不同的模型,例如开发实践或持续交付。成熟度模型中的每个级别都有明确的要求,这些要求遵循 SMART 准则,以便轻松衡量是否已达到要求。我发现一个很好的例子是持续交付。
  • 建立实践社区(CoP):在共同兴趣小组之间交流经验和知识有助于分发思想和标准化方法。例如,您可以每三个月左右将所有 JavaScript 开发人员和架构师聚集在一个房间中,讨论过去和当前的挑战以及如何解决它们或采用新的方法论和方法。架构师可以共享,讨论和调整其愿景,开发人员可以共享经验并向同行学习。这样的回合不仅可以为企业带来极大的好处,也可以为个人本身带来极大的好处,因为它有助于建立更强大的网络并传播思想。还可以查看 SAFe 框架中的文章实践社区,该文章在敏捷环境中解释了 CoP 概念。
  • 进行公开会议:误解或模棱两可的原因之一是缺乏沟通。阻止固定时间段,例如每周 30 分钟,用于与同行交流热门话题。本届会议没有任何议程可以讨论。尝试当场解决小事。安排对更复杂主题的后续行动。

(10)市场

您的想法很棒,您已经很好地传达了他们的想法,但仍然没人愿意遵循吗?那么您可能缺乏营销技巧。

  • 激励并说服:公司如何说服您购买产品?他们证明了它的价值和好处。但不仅仅是 5 点。他们包装得很好,并使其尽可能容易消化。

    1. 原型:显示您的想法的原型。有很多用于创建原型的工具。对于喜欢 SAP 的企业,请访问 build.me,在其中您可以快速轻松地创建外观漂亮且可单击的 UI5 应用程序。
    2. 使用视频:除了“无聊的幻灯片”,您还可以显示一个视频,该视频可以演示您的想法或至少是方向。但是请不要过度营销:从长远来看,内容为王。如果您的话不正确,从长远来看,这将损害您的声誉。
  • 为您的想法而奋斗并坚持不懈:人们有时不喜欢您的想法,或者他们懒得遵循。如果您真的对自己的想法深信不疑,则应不断追求并“奋斗”。有时这是必要的。具有长期目标的体系结构决策通常不是最容易的:开发人员不喜欢它们,因为它们的开发更加复杂。经理们不喜欢它们,因为它们在短期内更昂贵。这是您要坚持不懈并进行谈判的工作。

  • 寻找盟友:很难独自建立或执行您的想法,甚至是不可能的。尝试找到可以支持和说服他人的盟友。使用您的网络。如果还没有,请立即开始构建。您可以从与(思想开放的)同事讨论您的想法开始。如果他们喜欢它,或者至少喜欢它的一部分,那么如果别人提出来,他们很可能会支持您的想法(“ X 的想法很有趣。”)。如果他们不喜欢,问为什么:也许您错过了什么?还是您的故事不够令人信服?下一步是找到具有决定权的盟友。要求开放的讨论。如果您担心讨论,请记住,有时您需要离开舒适区。

  • 重复,相信它:“ […]研究表明,反复接触某个观点会使人们相信该观点更为普遍,即使该观点仅来自一个人。”(来源:《金融品牌》)经常发布一些消息,这可以帮助说服人们。但请注意:从我的角度来看,应该明智地使用这种策略,因为它可能适得其反,成为糟糕的营销技巧。

6. 架构师技术路线图

7. 参考

  1. SoftwareArchitect

Newegg Kafka 平台化的一点经验

本文基于IT技术圈(西安)10月份线下沙龙整理而来,略有删减。

扫码关注我

1. 前言

  • Newegg Kafka 使用规模
  • Newegg Kafka 使用场景
  • Newegg Kafka 平台化KafkaCenter
  • KafkaCenter 解决了什么问题
  • KafkaCenter 惊鸿一瞥
  • KafkaCenter 技术上的探索

2. Newegg Kafka 使用规模

我们是一家小公司,对Kafka的使用有限,这里我就放出我们系统的一个统计吧,数据截止到2019-10-30,仅统计目前已经接入Kafka平台管理的产线环境数据

每天指标如下:

MessagesIn BytesIn BytesOut
1.9b 2.26TB 12.23TB

3. Newegg Kafka 使用场景

3.1 Kafka 使用场景

  • 异步处理
  • 日常系统解耦
  • 削峰
  • 提速
  • 广播

3.2 Newegg Kafka 使用场景

  • 异构数据同步(redis/hbase/sqlserver/cassandra/solr/es)
  • 网站流量数据/日志数据
  • 流式处理

4. Newegg Kafka 平台化

这里最要介绍两个部分,一个是kafka的监控体系,一个是平台化门户KafkaCenter

4.1 集群监控告警体系

集群监控告警体系

图片3
图片4
图片5
图片6

4.2 KafkaCenter(面向用户+运维的)

Kafka Center是一个kafka治理平台,是EC Bigdata Team多年kafka使用经验的落地实践,整合集群管理,集群运维,生产监控,消费监控,周边生态等统一一站式解决方案。
图片7

kafkaCenter

5. KafkaCenter 解决了什么问题

5.1 我们面临的问题

  • 创建topic,人工处理化
  • 相关kafka运维,监控孤岛化
  • 现有消费监控工具监控不准确
  • 无法拿到Kafka 集群的summay信息
  • 无法快速知晓集群健康状态
  • 无法知晓业务对team kafka使用情况
  • kafka管理,监控工具稀少,没有一个好的工具我们直接可以使用
  • 无法快速查询topic消息

5.2 Kafka Center解决了哪些问题

  • 统一: 一个平台,一站式包含自助,管理,监控,运维,使用一体化。
  • 流程化: 创建topic流程化,做到对topic使用全生命周期管理。
  • 复用: 平台支持接入多个集群,复用性很高。
  • 成本: 只用部署一套程序,节省机器资源。降低运维成本,高效运维。
  • 生态: 目前已经接入connect,未来即将接入ksql。
  • 便捷: 提供便捷工具,让无需有kafka使用经验的人,都可以方便生产、消费消息。
  • 全局: 可以站在不同的维度查看目前kafka使用情况
  • 权限: 完善的权限设计,减少风险漏洞。

5. KafkaCenter 惊鸿一瞥

功能模块图
核心功能预览

图片10
图片11
图片12
图片13
图片14
图片15
图片16

6. KafkaCenter 技术上的探索

在实现功能的基础外,我们还做了更多工程与技术上的的探索,这里就做些删减,如果想了解更多的内容,可以私信我。

  • Kafka消费监控算法
  • 前后端技术栈完全分离
  • CI/CD持续集成与发布
  • 跨数据中心监控解决方案

通常使用的kafka的用户都关注与消费延迟,对于延迟Lag的计算,是很多用户关心的,这里就简单说一下如何计算Lag.

在计算Lag之前先普及几个基本常识

LEO(LogEndOffset): 这里说的和官网说的LEO有点区别,主要是指对consumer可见的offset.即HW(High Watermark)

CURRENT-OFFSET: consumer消费到的具体位移
知道以上信息后,可知Lag=LEO-CURRENT-OFFSET。计算出来的值即为消费延迟情况。

6.1 Kafka消费监控算法

6.1.1 broker消费方式 offset 获取

实现思路

  1. 根据topic 获取消费该topic的group
  2. 通过使用KafkaAdminClient的describeConsumerGroups读取broker上指定group和topic的消费情况,可以获取到clientId,CURRENT-OFFSET,patition,host等
  3. 通过consumer获取LogEndOffset(可见offset)
  4. 将2与3处信息合并,计算Lag

6.1.2 zk消费方式 offset 获取

实现思路

  1. 根据topic 获取消费该topic的group
  2. 读取zookeeper上指定group和topic的消费情况,可以获取到clientId,CURRENT-OFFSET,patition。
  3. 通过consumer获取LogEndOffset(可见offset)
  4. 将2与3处信息合并,计算Lag

6.2 前后端技术栈完全分离

  • 服务端Springboot
  • 前端icework(React完整解决方案)

6.3 CI/CD持续集成与发布

6.4 跨数据中心监控解决方案

部署架构

0%