GreenLightt

GreenLightt 查看完整档案

南京编辑克莱登大学  |  软件工程 编辑NULL  |   NULL 编辑 greenlightt.github.io/ 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

GreenLightt 发布了文章 · 2020-04-10

Flink 实战 kafka 写数据到 hbase

需求

此次 job 任务的目的是从 kafka 指定 topic 读取消息,并写入到 hbase 中;

消息体包含 project(消息所属项目)、table(要写入的 hbase 表名)和 datajson 字符串)。

执行思路:

  1. 使用流式引擎,配置 kafka source
  2. 过滤不符合格式要求的数据
  3. 调整触发机制:指定时间窗口,同时若在窗口内达成指定次数,也会触发
  4. 接入 hbase sink 批量写数据

环境准备

version: '3'
services:
  mysql:
    image: "docker.io/mysql:5.7"
    environment:
      MYSQL_ROOT_PASSWORD: "123456"
    ports:
      - "3306:3306"
  zookeeper:
    image: harbor.oneitfarm.com/cidata/zookeeper:3.4.14
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=0.0.0.0:2888:3888
      ENABLE_SASL: "true"
      SUPER_PASSWORD: admin
      USER_KAFKA_PASSWORD: 123456
    ports:
      - "2182:2181"
  kafka_broker:
    image: "harbor.oneitfarm.com/cidata/kafka:2.4.0"
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      ZOOKEEPER_SASL_ENABLE: "true"
      ZOOKEEPER_SASL_USER_KAFKA_PASSWORD: 123456
      KAFKA_SASL_ENABLE: "true"
      KAFKA_ADMIN_PASSWORD: 123456
      KAFKA_BROKER_ID: 1
      KAFKA_HEAP_OPTS: "-Xmx512M -Xms256M"
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:SASL_PLAINTEXT,OUTSIDE:SASL_PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: INSIDE://:9094,OUTSIDE://${HOST_IP}:19092
      KAFKA_LISTENERS: INSIDE://:9094,OUTSIDE://:9092
      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
    ports:
      - "19092:9092"

同目录下要有 .env 文件

# 修改成主机IP
HOST_IP=192.168.50.187

代码

pom.xml

    <properties>
        <mainClass>xxx.flinkjob.kafka.Application</mainClass>
        <flink-version>1.10.0</flink-version>
        <hbase-version>1.3.1</hbase-version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink-version}</version>
<!--            <scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.11</artifactId>
            <version>${flink-version}</version>
<!--            <scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-jdbc_2.11</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_2.11</artifactId>
            <version>${flink-version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase-version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-common</artifactId>
            <version>${hbase-version}</version>
        </dependency>
        <dependency>
            <groupId>commons-cli</groupId>
            <artifactId>commons-cli</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>${mainClass}</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>

                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

model

package xxx.flinkjob.kafka.model;

public class HttpDataModel {
    private String project;
    private String table;
    private String data;

    public HttpDataModel() {}

    public HttpDataModel(String project, String table, String data) {
        this.project = project;
        this.table   = table;
        this.data    = data;
    }

    public String getProject() {
        return project;
    }

    public String getTable() {
        return table;
    }

    public String getData() {
        return data;
    }

    public String getFullTable() {
        return project + ":" + table;
    }

    public void setProject(String project) {
        this.project = project;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public void setData(String data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "HttpDataModel{" +
                "project='" + project + '\'' +
                ", table='" + table + '\'' +
                ", data='" + data + '\'' +
                '}';
    }
}

sink

package xxx.flinkjob.kafka.sink;

import com.alibaba.fastjson.JSONObject;
import xxx.flinkjob.kafka.model.HttpDataModel;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.log4j.Logger;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HbaseSink extends RichSinkFunction<List<HttpDataModel>> implements Serializable {
    private Logger log;

    private String hbase_zookeeper_host;
    private String hbase_zookeeper_port;

    private Connection connection;
    private Admin admin;

    public HbaseSink(String hbase_zookeeper_host, String hbase_zookeeper_port) {
        this.hbase_zookeeper_host = hbase_zookeeper_host;
        this.hbase_zookeeper_port = hbase_zookeeper_port;
    }

    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        log = Logger.getLogger(HbaseSink.class);

        org.apache.hadoop.conf.Configuration configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.property.clientPort", hbase_zookeeper_port);
        configuration.set("hbase.zookeeper.quorum", hbase_zookeeper_host);

        connection = ConnectionFactory.createConnection(configuration);
        admin = connection.getAdmin();
    }

    public void invoke(List<HttpDataModel> datas, Context context) throws Exception {
        // 按 project:table 归纳
        Map<String, List<HttpDataModel>> map = new HashMap<String, List<HttpDataModel>>();
        for (HttpDataModel data : datas) {
            if (! map.containsKey(data.getFullTable())) {
                map.put(data.getFullTable(), new ArrayList<HttpDataModel>());
            }
            map.get(data.getFullTable()).add(data);
        }
        // 遍历 map
        for(Map.Entry<String, List<HttpDataModel>> entry : map.entrySet()){
            // 如果 表不存在,即创建
            createTable(entry.getKey());
            // 写数据
            List<Put> list = new ArrayList<Put>();
            for (HttpDataModel item : entry.getValue()) {
                Put put = new Put(Bytes.toBytes(String.valueOf(System.currentTimeMillis())));

                JSONObject object = JSONObject.parseObject(item.getData());
                for (String key: object.keySet()) {
                    put.addColumn("data".getBytes(), key.getBytes(), object.getString(key).getBytes());
                }
                list.add(put);
            }
            connection.getTable(TableName.valueOf(entry.getKey())).put(list);
        }
    }

    @Override
    public void close() throws Exception {
        super.close();
    }

    /**
     * 创建 hbase 表
     */
    private void createTable(String tableName) throws Exception {
        createNamespace(tableName.split(":")[0]);
        TableName table = TableName.valueOf(tableName);
        if (! admin.tableExists(table)) {
            HTableDescriptor hTableDescriptor = new HTableDescriptor(table);
            // 固定只有 data 列簇
            hTableDescriptor.addFamily(new HColumnDescriptor("data"));
            admin.createTable(hTableDescriptor);
        }
    }

    /**
     * 创建命名空间
     */
    private void createNamespace(String namespace) throws Exception {
        try {
            admin.getNamespaceDescriptor(namespace);
        } catch (NamespaceNotFoundException e) {
            admin.createNamespace(NamespaceDescriptor.create(namespace).build());
        }
    }
}

trigger

package xxx.flinkjob.kafka.trigger;

import org.apache.flink.streaming.api.windowing.triggers.Trigger;
import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;

public class CountTrigger<T> extends Trigger<T, TimeWindow> {

    // 当前的计数标志
    private static int flag = 0;

    // 最大数量
    public static int threshold = 0;

    public CountTrigger(Integer threshold) {
        this.threshold = threshold;
    }

    /**
     * 添加到窗口的每个元素都会调此方法
     */
    public TriggerResult onElement(Object element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {
        ctx.registerEventTimeTimer(window.maxTimestamp());

        flag++;

        if(flag >= threshold){
            flag = 0;
            ctx.deleteProcessingTimeTimer(window.maxTimestamp());
            return TriggerResult.FIRE_AND_PURGE;
        }

        return TriggerResult.CONTINUE;
    }

    /**
     * 当注册的处理时间计时器触发时,将调用此方法
     */
    public TriggerResult onProcessingTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {
        if(flag > 0){
            // System.out.println("到达窗口时间执行触发:" + flag);
            flag = 0;
            return TriggerResult.FIRE_AND_PURGE;
        }
        return TriggerResult.CONTINUE;
    }

    /**
     * 当注册的事件时间计时器触发时,将调用此方法
     */
    public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {
        if (time >= window.maxTimestamp() && flag > 0) {
            // System.out.println("到达时间窗口且有数据,触发操作!");
            flag = 0;
            return TriggerResult.FIRE_AND_PURGE;
        } else if (time >= window.maxTimestamp() && flag == 0) {
            // 清除窗口但不触发
            return TriggerResult.PURGE;
        }
        return TriggerResult.CONTINUE;
    }

    /**
     * 执行任何需要清除的相应窗口
     */
    public void clear(TimeWindow window, TriggerContext ctx) throws Exception {
        ctx.deleteProcessingTimeTimer(window.maxTimestamp());
        ctx.deleteEventTimeTimer(window.maxTimestamp());
    }
}

入口执行文件

package xxx.flinkjob.kafka;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ci123.data.flinkjob.kafka.model.HttpDataModel;
import com.ci123.data.flinkjob.kafka.sink.HbaseSink;
import com.ci123.data.flinkjob.kafka.trigger.CountTrigger;
import org.apache.commons.cli.*;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.AllWindowFunction;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.Window;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.util.Collector;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class Application {

    @SuppressWarnings(value={"unchecked"})
    public static void main(String[] args) throws Exception {
        // kafka 需要的参数
        String brokers = "127.0.0.1:9092";
        String username = "admin";
        String password = "123456";
        String topic = "test";
        // hbase 需要的参数
        String hbase_zookeeper_host = "hbase";
        String hbase_zookeeper_port = "2181";

        // 接收命令行参数,覆盖默认值
        Options options = new Options();
        options.addOption("kafka_brokers", true, "kafka cluster hosts, such 127.0.0.1:9092");
        options.addOption("kafka_username", true, "kafka cluster username, default: admin");
        options.addOption("kafka_user_password", true, "kafka cluster user password, default: 123456");
        options.addOption("kafka_topic", true, "kafka cluster topic, default: test");

        options.addOption("hbase_zookeeper_host", true, "hbase zookeeper host, default: hbase");
        options.addOption("hbase_zookeeper_port", true, "hbase zookeeper port, default: 2181");

        CommandLineParser parser = new DefaultParser();
        CommandLine line = parser.parse( options, args );

        if ( line.hasOption( "kafka_brokers" ) ) {
            brokers = line.getOptionValue("kafka_brokers");
        } else {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp( "flink write hbase job", options );
            System.exit(1);
        }

        if ( line.hasOption( "kafka_username" ) ) {
            username = line.getOptionValue("kafka_username");
        }
        if ( line.hasOption( "kafka_user_password" ) ) {
            password = line.getOptionValue("kafka_user_password");
        }
        if ( line.hasOption( "kafka_topic" ) ) {
            topic = line.getOptionValue("kafka_topic");
        }
        if ( line.hasOption( "hbase_zookeeper_host" ) ) {
            hbase_zookeeper_host = line.getOptionValue("hbase_zookeeper_host");
        }
        if ( line.hasOption( "hbase_zookeeper_port" ) ) {
            hbase_zookeeper_port = line.getOptionValue("hbase_zookeeper_port");
        }

        // 执行任务
        doExcute(brokers, username, password, topic, hbase_zookeeper_host, hbase_zookeeper_port);
    }

    /**
     * 具体任务执行
     */
    public static void doExcute(String kafka_brokers, String kafka_username, String kafka_password,
                                String topic, String hbase_zookeeper_host, String hbase_zookeeper_port) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 设置 kafka source
        env.enableCheckpointing(5000 * 100000);

        Properties props = getKafkaProperties(kafka_username, kafka_password);
        props.setProperty("bootstrap.servers", kafka_brokers);
        DataStream<String> stream = env.addSource(new FlinkKafkaConsumer(topic, new SimpleStringSchema(), props));

        // 过滤不标准格式的数据,并格式化
        DataStream<HttpDataModel> formated_stream = stream.filter(s -> {
            JSONObject obj = JSONObject.parseObject(s);
            return obj.containsKey("project") && obj.containsKey("table") && obj.containsKey("data");
        }).map(s -> { return JSON.parseObject(s, HttpDataModel.class); });

        // 在 10 秒的时间窗口内,每 100 条触发输出到 hbase
        DataStream<List<HttpDataModel>> batch_stream = formated_stream
                .timeWindowAll(Time.seconds(10))
                .trigger(new CountTrigger(100))
                .apply(new AllWindowFunction<HttpDataModel, List<HttpDataModel>, Window>() {
                    public void apply(Window window, Iterable<HttpDataModel> values, Collector<List<HttpDataModel>> out) throws Exception {
                        List<HttpDataModel> lists = new ArrayList<HttpDataModel>();
                        for (HttpDataModel value : values) {
                            lists.add(value);
                        }
                        out.collect(lists);
                    }
                });

        batch_stream.addSink(new HbaseSink(hbase_zookeeper_host, hbase_zookeeper_port));

        // 控制台输出
        //batch_stream.print();

        env.execute("integration-http");
    }

    /**
     * 获取 kafka 的默认配置
     */
    public static Properties getKafkaProperties(String username, String password) {
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "127.0.0.1:9092");
        props.setProperty("group.id", "dataworks-integration");
        props.put("enable.auto.commit", "true");
        props.put("auto.offset.reset", "earliest");
        props.put("auto.commit.interval.ms", "1000");
        props.put("session.timeout.ms", "30000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        String jaasTemplate = "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"%s\" password=\"%s\";";
        String jaasCfg = String.format(jaasTemplate, username, password);

        props.put("security.protocol", "SASL_PLAINTEXT");
        props.put("sasl.mechanism", "SCRAM-SHA-256");
        props.put("sasl.jaas.config", jaasCfg);
        return props;
    }
}
查看原文

赞 3 收藏 2 评论 0

GreenLightt 关注了专栏 · 2020-04-01

煎鱼的清汤锅

今天写代码了吗 :-) 博客地址:https://github.com/EDDYCJY/blog

关注 7546

GreenLightt 关注了用户 · 2020-04-01

煎鱼 @eddycjy

我的公众号:脑子进煎鱼了
博客地址:https://eddycjy.com/
喝口热水,写写代码。

关注 1988

GreenLightt 赞了文章 · 2020-04-01

首发特性:goproxy.cn 现已推出首个 Go 模块代理统计数据 API

前言

这周很值得纪念,首先是 goproxy.cn 已缓存的模块版本总数突破了一百万,这甚至比起 Go 官方的 proxy.golang.org 已缓存的还要多出不少。其次是 goproxy.cn 的日访问量已稳定在千万级,正在朝着亿级进发,目前这在国内我了解到的别的几个同类服务中是最多的了。另外尤为重要的一点是,经过我们的不懈努力,甚至付出了两次事故的代价(详见:status.goproxy.cn),我们终于使得 goproxy.cn 达到了零错误率!现在的 goproxy.cn 可以说是很稳的了,大家可以放心使用。

刚刚在开头稍微为这一个月发生的事情做了个小总结。接下来说一下本次的重点,也就是 goproxy.cn 推出了 Go 模块代理世界中的首个统计数据 API(文档详见:goproxy.cn/stats),这是个原本计划在二月底就发布的特性。统计数据 API 其实很早就开发完了(几个月前就基本完工了),但却迟迟未发布,主要原因是跟我们前一阵子在探索零错误率相关,我们想在一个完美的时间点把它介绍给大家。这一阵子有很多大佬催过我,比如光无闻大哥一人就已经催过我很多次了……在这里统一道个歉……当然,迟到总比不到好,让大家久等了!

目前先推出了 4 种呼声较高的 API,后续可能还会有其他 API 被添加进来。

注意:目前 goproxy.cn 的统计数据 API 所展示的只是最近三个月的数据。

API:获取服务摘要

首先是一个用于获取服务摘要信息的 API,通过调用这个 API 你可以获取到 goproxy.cn 中诸如所有模块版本的总尺寸和总数等信息。

获取服务摘要 API 的 URL 是固定的,没有路径参数:goproxy.cn/stats/summary

{
    "cacher_size": 2663405247231,
    "module_version_count": 1035421,
    "module_host_count": 1120,
    "top_10_module_hosts": [
        {"module_host": "github.com", "module_version_count": 921606},
        {"module_host": "k8s.io", "module_version_count": 24982},
        {"module_host": "gitlab.com", "module_version_count": 13172},
        {"module_host": "gopkg.in", "module_version_count": 10479},
        {"module_host": "golang.org", "module_version_count": 8000},
        {"module_host": "gitee.com", "module_version_count": 4650},
        {"module_host": "bitbucket.org", "module_version_count": 4177},
        {"module_host": "sigs.k8s.io", "module_version_count": 3280},
        {"module_host": "google.golang.org", "module_version_count": 2703},
        {"module_host": "istio.io", "module_version_count": 2487}
    ]
}

上面的示例响应中各字段的含义如下:

  • cacher_size:所有模块版本的总尺寸,单位是“字节”
  • module_version_count:所有模块版本的总数
  • module_host_count:模块主机地址的总数
  • top_10_module_hosts:所拥有的模块版本数排名前十的模块主机地址
  • top_10_module_hosts.module_host:模块主机地址
  • top_10_module_hosts.module_version_count:模块主机地址所拥有的模块版本总数

API:获取模块趋势

然后呼声蛮高的一个 API 就是获取服务中的模块趋势,有很多人好奇自己的模块在 goproxy.cn 中的活跃度排名究竟是怎样的。

目前我们提供了三类趋势,它们均只返回一段时间内的最活跃的最多前 1000 个模块:

  • goproxy.cn/stats/trends/latest:获取最新趋势
  • goproxy.cn/stats/trends/last-7-days:获取最近 7 天的趋势
  • goproxy.cn/stats/trends/last-30-days:获取最近 30 天的趋势
[
    {"module_path": "golang.org/x/sys", "download_count": 1822180},
    {"module_path": "golang.org/x/net", "download_count": 1713080},
    {"module_path": "golang.org/x/tools", "download_count": 1503522},
    {"module_path": "golang.org/x/crypto", "download_count": 1032270},
    {"module_path": "gopkg.in/yaml.v2", "download_count": 578120}
]

上面的示例响应中各字段的含义如下:

  • module_path:模块路径
  • download_count:模块路径对应的所有模块版本的总下载次数

API:获取模块(版本)统计

当然,我们支持获取服务中指定模块(版本)的统计,这也算是统计数据 API 的刚需了。

获取模块(版本)统计的 API 的 URL 格式为 goproxy.cn/stats/<module-path>[@<module-version>]。如 goproxy.cn/stats/golang.org/x/text 表示获取模块路径为 golang.org/x/text 的模块的所有模块版本的总统计,而 goproxy.cn/stats/golang.org/x/text@v0.3.2 表示获取该模块的 v0.3.2 版本的统计。

{
    "download_count": 476705,
    "last_30_days": [
        {"date": "2020-03-25T00:00:00Z", "download_count": 15940},
        {"date": "2020-03-24T00:00:00Z", "download_count": 16884},
        {"date": "2020-03-23T00:00:00Z", "download_count": 15842},
        {"date": "2020-03-22T00:00:00Z", "download_count": 11232},
        {"date": "2020-03-21T00:00:00Z", "download_count": 11894},
        {"date": "2020-03-20T00:00:00Z", "download_count": 14968},
        {"date": "2020-03-19T00:00:00Z", "download_count": 11017},
        {"date": "2020-03-18T00:00:00Z", "download_count": 5209},
        {"date": "2020-03-17T00:00:00Z", "download_count": 5759},
        {"date": "2020-03-16T00:00:00Z", "download_count": 4166},
        {"date": "2020-03-15T00:00:00Z", "download_count": 2145},
        {"date": "2020-03-14T00:00:00Z", "download_count": 2218},
        {"date": "2020-03-13T00:00:00Z", "download_count": 4609},
        {"date": "2020-03-12T00:00:00Z", "download_count": 5503},
        {"date": "2020-03-11T00:00:00Z", "download_count": 5412},
        {"date": "2020-03-10T00:00:00Z", "download_count": 6453},
        {"date": "2020-03-09T00:00:00Z", "download_count": 4798},
        {"date": "2020-03-08T00:00:00Z", "download_count": 2323},
        {"date": "2020-03-07T00:00:00Z", "download_count": 2332},
        {"date": "2020-03-06T00:00:00Z", "download_count": 4806},
        {"date": "2020-03-05T00:00:00Z", "download_count": 6339},
        {"date": "2020-03-04T00:00:00Z", "download_count": 5858},
        {"date": "2020-03-03T00:00:00Z", "download_count": 5579},
        {"date": "2020-03-02T00:00:00Z", "download_count": 8862},
        {"date": "2020-03-01T00:00:00Z", "download_count": 2119},
        {"date": "2020-02-29T00:00:00Z", "download_count": 2053},
        {"date": "2020-02-28T00:00:00Z", "download_count": 4735},
        {"date": "2020-02-27T00:00:00Z", "download_count": 6578},
        {"date": "2020-02-26T00:00:00Z", "download_count": 4954},
        {"date": "2020-02-25T00:00:00Z", "download_count": 5318}
    ],
    "top_10_module_versions": [
        {"module_version": "v0.3.2", "download_count": 232795},
        {"module_version": "v0.3.0", "download_count": 146009},
        {"module_version": "v0.3.1-0.20180807135948-17ff2d5776d2", "download_count": 67096},
        {"module_version": "v0.3.1-0.20181227161524-e6919f6577db", "download_count": 11494},
        {"module_version": "v0.0.0-20160726164857-2910a502d2bf", "download_count": 11223},
        {"module_version": "v0.0.0-20170915032832-14c0d48ead0c", "download_count": 3991},
        {"module_version": "v0.3.1-0.20171227012246-e19ae1496984", "download_count": 1128},
        {"module_version": "v0.0.0-20170915090833-1cbadb444a80", "download_count": 937},
        {"module_version": "v0.3.1-0.20181030141323-6f44c5a2ea40", "download_count": 480},
        {"module_version": "v0.3.1", "download_count": 353}
    ]
}

上面的示例响应中各字段的含义如下:

  • download_count:模块(版本)的总下载次数
  • last_30_days:模块(版本)最近 30 天的统计
  • last_30_days.date:模块(版本)统计的日期
  • last_30_days.downlaod_count:模块(版本)单日的下载次数
  • top_10_module_versions:该字段仅在根据模块路径获取其对应的所有模块版本的总统计时才会返回,表示其下载次数排行前十的模块版本
  • top_10_module_versions.module_version:模块版本
  • top_10_module_versions.download_count:模块版本的下载次数

API:获取模块总下载次数徽章

为了使模块作者们能更好地展示自己的模块在 goproxy.cn 的总下载次数,我们支持了 SVG 徽章。

获取模块总下载次数徽章的 API 的 URL 格式为 goproxy.cn/stats/<module-path>/badges/download-count.svg

你可以通过下述 Markdown 语法轻松地将指定模块的总下载次数徽章放入项目 README.md 中:

[![goproxy.cn](https://goproxy.cn/stats/<module-path>/badges/download-count.svg)](https://goproxy.cn)

比如 goproxy.cn/stats/golang.org/x/text/badges/download-count.svg

goproxy.cn

结语

由于我们是首家支持统计数据 API 的 Go 模块代理,所以我们预留了一段时间的功能探索期,在此期间内我们欢迎大家随时通过 goproxy.cn/stats 页面中提到的联系方式来分享你们的意见建议,一起为咱们中国 Go 语言社区的这个 Go 模块代理打造出一个功能性良好的统计数据 API。

最后再强调一点,goproxy.cn 由七牛运营,服务于 Go 语言社区,它永久免费,也一如既往地不加以任何形式的带宽限制、速率限制,大家可以敞开了用、放心地用。有问题?问;有建议?提。我们一直那里,欢迎随时来访。

查看原文

赞 7 收藏 1 评论 0

GreenLightt 提出了问题 · 2020-01-15

zookeeper 的 ssl 需要的证书该怎么生成?

实在不知道该怎么生成 zookeeper 需要的证书?

尝试过以下:

# 生成服务器 keystore (密钥和证书)
keytool -genkeypair -keystore server.keystore.jks -alias localhost -keyalg RSA -keysize 2048 -validity 365 -storepass store123456 -keypass key123456   -dname "CN=localhost, OU=Zookeeper, O=Apache, L=Unknow, ST=Unknow, C=Unknow"

# 导出证书
keytool -export -keystore server.keystore.jks -alias localhost -storepass store123456 -file server.crt

# 导入到服务器信任证书
keytool -v -keystore server.truststore.jks -alias localhost -import -file server.crt -storepass store123456 <<!
y
!

然后得到的 server.keystore.jksserver.truststore.jks 并不能用,报以下错误:

Caused by: javax.net.ssl.SSLHandshakeException: no cipher suites in common
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1647)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:318)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:306)
    at sun.security.ssl.ServerHandshaker.chooseCipherSuite(ServerHandshaker.java:1127)
    at sun.security.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:814)
    at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:221)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:970)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:967)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1459)
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1457)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1365)
    ... 18 more

关注 2 回答 0

GreenLightt 赞了文章 · 2019-12-11

一键安装kubernetes1.17.0

快速开始

环境信息
主机名IP地址
master0192.168.0.2
master1192.168.0.3
master2192.168.0.4
node0192.168.0.5

服务器密码:123456

kubernetes高可用安装教程
只需要准备好服务器,在任意一台服务器上执行下面命令即可
# 下载并安装sealos, sealos是个golang的二进制工具,直接下载拷贝到bin目录即可
wget https://github.com/fanux/sealos/releases/download/v3.0.1/sealos && \
    chmod +x sealos && mv sealos /usr/bin 
        
# 下载离线资源包
wget https://sealyun.oss-cn-beijing.aliyuncs.com/413bd3624b2fb9e466601594b4f72072-1.17.0/kube1.17.0.tar.gz

# 安装一个三master的kubernetes集群
sealos init --passwd 123456 \
    --master 192.168.0.2  --master 192.168.0.3  --master 192.168.0.4  \
    --node 192.168.0.5 \
    --pkg-url  /root/kube1.17.0.tar.gz \
    --version v1.17.0
参数含义
参数名含义示例
passwd服务器密码123456
masterk8s master节点IP地址192.168.0.2
nodek8s node节点IP地址192.168.0.3
pkg-url离线资源包地址,支持下载到本地,或者一个远程地址/root/kube1.16.0.tar.gz
version资源包对应的版本v1.16.0

就没有然后了
kubernetes一键HA

查看原文

赞 1 收藏 0 评论 0

GreenLightt 关注了用户 · 2019-12-11

iriniland @iriniland

13年研发经验,曾就职于蘑菇街、中国数码上市公司担任架构师/开发主管等职位,具有亿级分布式高并发架构设计和开发经验,专注于大数据WEB架构,与大家分享自己的技术与职场经验。

关注 5

GreenLightt 赞了文章 · 2019-12-11

Zookeeper vs Etcd

Zookeeper 和 Etcd 都是非常优秀的分布式协调系统,zookeeper 起源于 Hadoop 生态系统,etcd 的流行是因为它是 kubernetes 的后台支撑。

本文将会说明 zookeeper 和 etcd 的优缺点,以便于您根据实际需求选择更合适的分布式协调系统。

1. Zookeeper

概述

zookeeper 起源于 Hadoop,后来进化为 Apache 的顶级项目。现在已经被广泛使用在 Apache 的项目中,例如 Hadoop,kafka,solr 等等。

Zookeeper Architecture

zookeeper 使用 ZAB 协议作为其一致性协议。
zookeeper 通过团队的形式工作,一组 node 一起工作,来提供分布式能力,这组 node 的数量需要是奇数。

第一个节点与其他节点沟通,选举出一个 leader,获取多数票数的成为 leader,这就是为什么需要奇数个 node,其他节点被称为follower。

client 连接 zookeeper 时可以连接任何一个,client 的读请求可以被任何一个节点处理,写请求只能被 leader 处理。所以,添加新节点可以提高读的速度,但不会提高写的速度。

对于 CAP 模型,zookeeper 保障的是 CP。

ZNode

ZNode Structure

存储数据时,zookeeper 使用树形结构,其中的每个节点称作 ZNode,访问一个 ZNode 时,需要提供从 root 开始的绝对路径。

每个 ZNode 可以存储最多 1MB 的数据,用户可以:

  • 创建 ZNode
  • 删除 ZNode
  • 存储数据到指定 ZNode
  • 从 ZNode 中读取数据

zookeeper 还提供了一个非常重要的特性:watcher API。

zookeeper watches

用户可以对一个 ZNode 设置 watch,当这个 ZNode 发生了变化时,例如 创建、删除、数据变更、添加或移除子节点,watch API 就会发出通知,这是 zookeeper 非常重要的功能。

zookeeper 的 watch 有一个缺点,就是这个 watch 只能被触发一次,一旦发出了通知,如果还想对这个节点继续 watch,用户需要重新设置 watch。

优点

  • 非阻塞全部快照(达成最终一致)
  • 高效的内存管理
  • 高可靠
  • API 简单
  • 连接管理可以自动重试
  • ZooKeeper recipes 的实现是经过完整良好的测试的。
  • 有一套框架使得写新的 ZooKeeper recipes 非常简单。
  • 支持监听事件
  • 发生网络分区时,各个区都会开始选举 leader,那么节点数少的那个分区将会停止运行。

缺点

  • zookeeper 是 java 写的,那么自然就会继承 java 的缺点,例如 GC 暂停。
  • 如果开启了快照,数据会写入磁盘,此时 zookeeper 的读写操作会有一个暂时的停顿。
  • 对于每个 watch 请求,zookeeper 都会打开一个新的 socket 连接,这样 zookeeper 就需要实时管理很多 socket 连接,比较复杂。

etcd

概述

etcd 是用 go 开发的,出现的时间并不长,不像 zookeeper 那么悠久和有名,但是前景非常好。

etcd 是因为 kubernetes 而被人熟知的,kubernetes 的 kube master 使用 etcd 作为分布式存储获取分布式锁,这为 etcd 的强大做了背书。

etcd 使用 RAFT 算法实现的一致性,比 zookeeper 的 ZAB 算法更简单。

etcd 没有使用 zookeeper 的树形结构,而是提供了一个分布式的 key-value 存储。

特性:

  • 原子性
  • 一致性
  • 顺序一致性
  • 可串行化级别
  • 高可用
  • 可线性化

API

etcd3 提供了如下操作接口:

  • put - 添加一个新的 key-value 到存储中
  • get - 获取一个 key 的 value
  • range - 获取一个范围的 key 的 value,例如:key1 - key10
  • transaction - 读、对比、修改、写的组合
  • watch - 监控一个或一个范围的 key,发生变化后就会得到通知

优点

  • 支持增量快照,避免了 zookeeper 的快照暂停问题
  • 堆外存储,没有垃圾回收暂停问题
  • 无需像 zookeeper 那样为每个 watch 都做个 socket 连接,可以复用
  • zookeeper 每个 watch 只能收到一次事件通知,etcd 可以持续监控,在一次 watch 触发之后无需再次设置一次 watch
  • zookeeper 会丢弃事件,etcd3 持有一个事件窗口,在 client 断开连接后不会丢失所有事件

缺点

  • 如果超时,或者 client 与 etcd 网络中断,client 不会明确的知道当前操作的状态
  • 在 leader 选举时,etcd 会放弃操作,并且不会给 client 发送放弃响应
  • 在网络分区时,当 leader 处于小分区时,读请求会继续被处理

总结

zookeeper 是用 java 开发的,被 Apache 很多项目采用。

etcd 是用 go 开发的,主要是被 Kubernetes 采用。

zookeeper 非常稳定,是一个著名的分布式协调系统,etcd 是后起之秀,前景广阔。

因为 etcd 是用 go 写的,现在还没有很好的 java 客户端库,需要通过 http 方式调用。

而 zookeeper 在这方面就成熟很多,对于 java 之外的其他开发语言都有很好的客户端库。

具体选择 zookeeper 还是 etcd,需要根据您的需求结合它们各自的特性进行判断,还有您所使用的开发语言。

翻译整理自:

https://medium.com/@Imesha94/...

查看原文

赞 2 收藏 1 评论 0

GreenLightt 赞了文章 · 2019-10-18

阿里毕玄:程序员如何提升自己的硬实力

从业余程序员到职业程序员

程序员刚入行时,我觉得最重要的是把自己培养成职业的程序员。

我的程序员起步比同龄人都晚了很多,更不用说现在的年轻人了。我大学读的是生物专业,在上大学前基本算是完全没接触过计算机。军训的时候因为很无聊,我和室友每天跑去学校的机房玩,我现在还印象很深刻,我第一次走进机房的时候,别人问,你是要玩windows,还是dos,我那是完全的一抹黑。后来就只记得在机房一堆人都是在练习盲打,军训完,盲打倒是练的差不多了,对计算机就这么产生了浓厚的兴趣,大一的时候都是玩组装机,捣鼓了一些,对计算机的硬件有了那么一些了解。

到大二后,买了一些书开始学习当时最火的网页三剑客,学会了手写HTML、PS的基本玩法之类的,课余、暑假也能开始给人做做网站什么的(那个时候做网站真的好赚钱),可能那样过了个一年左右,做静态的网页就不好赚钱了,也不好找实习工作,于是就开始学asp,写些简单的CRUD,做做留言板、论坛这些动态程序,应该算是在这个阶段接触编程了。

毕业后加入了深圳的一家做政府行业软件的公司,一个非常靠谱和给我空间的Leader,使得自己在那几年有了不错的成长,终于成了一个职业的程序员。

通常来说,业余或半职业的程序员,多数是1个人,或者很小的一个团队一起开发,使得在开发流程、协作工具(例如jira、cvs/svn/git等)、测试上通常会有很大的欠缺,而职业的程序员在这方面则会专业很多。另外,通常职业的程序员做的系统都要运行较长的时间,所以在可维护性上会特别注意,这点我是在加入阿里后理解更深的。一个运行10年的系统,和一个写来玩玩的系统显然是有非常大差别的。

这块自己感觉也很难讲清楚,只能说模模糊糊有个这样的概念。通常在有兴趣的基础上,从业余程序员跨越到成为职业程序员我觉得不会太难。

编程能力的成长

作为程序员,最重要的能力始终是编程能力,就我自己的感受而言,我觉得编程能力的成长主要有这么几个部分:

1、编程能力初级:会用

编程,首先都是从学习编程语言的基本知识学起的,不论是什么编程语言,有很多共同的基本知识,例如怎么写第一个Hello World、if/while/for、变量等,因此我比较建议在刚刚开始学一门编程语言的时候,看看编程语言自己的一些文档就好,不要上来就去看一些高阶的书。我当年学Java的时候上来就看Think in Java、Effective Java之类的,真心好难懂。

除了看文档以外,编程是个超级实践的活,所以一定要多写代码,只有这样才能真正熟练起来。这也是为什么我还是觉得在面试的时候让面试者手写代码是很重要的,这个过程是非常容易判断写代码的熟悉程度的。很多人会说由于写代码都是高度依赖IDE的,导致手写很难,但我绝对相信写代码写了很多的人,手写一段不太复杂的、可运行的代码是不难的。即使像我这种三年多没写过代码的人,让我现在手写一段不太复杂的可运行的Java程序,还是没问题的,前面N年的写代码生涯使得很多东西已经深入骨髓了。

我觉得编程能力初级这个阶段对于大部分程序员来说都不会是问题,勤学苦练,是这个阶段的核心。

2、编程能力中级:会查和避免问题

除了初级要掌握的会熟练的使用编程语言去解决问题外,中级我觉得首先是提升查问题的能力。

在写代码的过程中,出问题是非常正常的,怎么去有效且高效的排查问题,是程序员群体中通常能感受到的大家在编程能力上最大的差距。

解决问题能力强的基本很容易在程序员群体里得到很高的认可。在查问题的能力上,首先要掌握的是一些基本的调试技巧,好用的调试工具,在Java里有JDK自带的jstat、jmap、jinfo,不在JDK里的有mat、gperf、btrace等。工欲善其事必先利其器,在查问题上是非常典型的,有些时候大家在查问题时的能力差距,有可能仅仅是因为别人比你多知道一个工具而已。

除了调试技巧和工具外,查问题的更高境界就是懂原理。一个懂原理的程序员在查问题的水平上和其他程序员是有明显差距的。我想很多的同学应该能感受到,有些时候查出问题的原因仅仅是因为有效的工具,知其然不知其所以然。

我给很多阿里的同学培训过Java排查问题的方法,在这个培训里,我经常也会讲到查问题的能力的培养最主要的也是熟练,多尝试给自己写一些会出问题的程序,多积极的看别人是怎么查问题的,多积极的去参与排查问题,很多最后查问题能力强的人多数仅仅是因为“无他,但手熟尔”。

我自己排查问题能力的提升主要是在2009年和2010年。那两年作为淘宝消防队(处理各种问题和故障的虚拟团队)的成员,处理了很多的故障和问题。当时消防队还有阿里最公认的技术大神——多隆,我向他学习到了很多排查问题的技巧。和他比,我排查问题的能力就是初级的那种。

印象最深刻的是一次我们一起查一个应用cpu us高的问题,我们两定位到是一段代码在某种输入参数的时候会造成cpu us高的原因后,我能想到的继续查的方法是去生产环境抓输入参数,然后再用参数来本地debug看是什么原因。但多隆在看了一会那段代码后,给了我一个输入参数,我拿这个参数一运行,果然cpu us很高!这种case不是一次两次。所以我经常和别人说,我是需要有问题场景才能排查出问题的,但多隆是完全有可能直接看代码就能看出问题的,这是本质的差距。

除了查问题外,更厉害的程序员是在写代码的过程就会很好的去避免问题。大家最容易理解的就是在写代码时处理各种异常情况,这里通常也是造成程序员们之间很大的差距的地方。

写一段正向逻辑的代码,大部分情况下即使有差距,也不会太大,但在怎么很好的处理这个过程中有可能出现的异常上,这个时候的功力差距会非常明显。很多时候一段代码里处理异常逻辑的部分都会超过正常逻辑的代码量。

我经常说,一个优秀程序员和普通程序员的差距,很多时候压根就不需要看什么满天飞的架构图,而只用show一小段的代码就可以。

举一个小case大家感受下。当年有一个严重故障,最后查出的原因是输入的参数里有一个是数组,把这个数组里的值作为参数去查数据库,结果前面输入了一个很大的数组,导致从数据库查了大量的数据,内存溢出了,很多程序员现在看都会明白对入参、出参的保护check,但类似这样的case我真的碰到了很多。

在中级这个阶段,我会推荐大家尽可能的多刻意的去培养下自己这两个方面的能力,成为一个能写出高质量代码、有效排查问题的优秀程序员。

3、编程能力高级:懂高级API和原理

就我自己的经历而言,我是在写了多年的Java代码后,才开始真正更细致的学习和掌握Java的一些更高级的API,我相信多数Java程序员也是如此。

我算是从2003年开始用Java写商业系统的代码,但直到在2007年加入淘宝后,才开始非常认真地学习Java的IO通信、并发这些部分的API。尽管以前也学过也写过一些这样的代码,但完全就是皮毛。当然,这些通常来说有很大部分的原因会是工作的相关性,多数的写业务系统的程序员可能基本就不需要用到这些,所以导致会很难懂这些相对高级一些的API,但这些API对真正的理解一门编程语言,我觉得至关重要。

在之前的程序员成长路线的文章里我也讲到了这个部分,在没有场景的情况下,只能靠自己去创造场景来学习好。我觉得只要有足够的兴趣,这个问题还是不大的,毕竟现在有各种开源,这些是可以非常好的帮助自己创造机会学习的,例如学Java NIO,可以自己基于NIO包一个框架,然后对比Netty,看看哪些写的是不如Netty的,这样会非常有助于真正的理解。

在学习高级API的过程中,以及排查问题的过程中,我自己越来越明白懂编程语言的运行原理是非常重要的,因此我到了后面的阶段开始学习Java的编译机制、内存管理、线程机制等。对于我这种非科班出身的而言,学这些会因为缺乏基础更难很多,但这些更原理性的东西学会了后,对自己的编程能力会有质的提升,包括以后学习其他编程语言的能力,学这些原理最好的方法我觉得是先看看一些讲相关知识的书,然后去翻看源码,这样才能真正的更好的掌握,最后是在以后写代码的过程中、查问题的过程中多结合掌握的原理,才能做到即使在N年后也不会忘。

在编程能力的成长上,我觉得没什么捷径。我非常赞同1万小时理论,在中级、高级阶段,如果有人指点或和优秀的程序员们共事,会好非常多。不过我觉得这个和读书也有点像,到了一定阶段后(例如高中),天分会成为最重要的分水岭,不过就和大部分行业一样,大部分的情况下都还没到拼天分的时候,只需要拼勤奋就好。

系统设计能力的成长

除了少数程序员会进入专深的领域,例如Linux Kernel、JVM,其他多数的程序员除了编程能力的成长外,也会越来越需要在系统设计能力上成长。

通常一个编程能力不错的程序员,在一定阶段后就会开始承担一个模块的工作,进而承担一个子系统、系统、跨多领域的更大系统等。

我自己在工作的第三年开始承担一个流程引擎的设计和实现工作,一个不算小的系统,并且也是当时那个项目里的核心部分。那个阶段我学会了一些系统设计的基本知识,例如需要想清楚整个系统的目标、模块的划分和职责、关键的对象设计等,而不是上来就开始写代码。但那个时候由于我是一个人写整个系统,所以其实对设计的感觉并还没有那么强力的感觉。

在那之后的几年也负责过一些系统,但总体感觉好像在系统设计上的成长没那么多,直到在阿里的经历,在系统设计上才有了越来越多的体会。(点击文末阅读原文,查看:我在系统设计上犯过的14个错,可以看到我走的一堆的弯路)。

在阿里有一次做分享,讲到我在系统设计能力方面的成长,主要是因为三段经历,负责专业领域系统的设计 -> 负责跨专业领域的专业系统的设计 -> 负责阿里电商系统架构级改造的设计。

第一段经历,是我负责HSF。HSF是一个从0开始打造的系统,它主要是作为支撑服务化的框架,是个非常专业领域的系统,放在整个淘宝电商的大系统来看,其实它就是一个很小的子系统,这段经历里让我最深刻的有三点:

1).要设计好这种非常专业领域的系统,专业的知识深度是非常重要的。我在最早设计HSF的几个框的时候,是没有设计好服务消费者/提供者要怎么和现有框架结合的,在设计负载均衡这个部分也反复了几次,这个主要是因为自己当时对这个领域掌握不深的原因造成的;

2). 太技术化。在HSF的阶段,出于情怀,在有一个版本里投入了非常大的精力去引进OSGi以及去做动态化,这个后来事实证明是个非常非常错误的决定,从这个点我才真正明白在设计系统时一定要想清楚目标,而目标很重要的是和公司发展阶段结合;

3). 可持续性。作为一个要在生产环境持续运行很多年的系统而言,怎么样让其在未来更可持续的发展,这个对设计阶段来说至关重要。这里最low的例子是最早设计HSF协议的时候,协议头里竟然没有版本号,导致后来升级都特别复杂;最典型的例子是HSF在早期缺乏了缺乏了服务Tracing这方面的设计,导致后面发现了这个地方非常重要后,全部落地花了长达几年的时间;又例如HSF早期缺乏Filter Chain的设计,导致很多扩展、定制化做起来非常不方便。

第二段经历,是做T4。T4是基于LXC的阿里的容器,它和HSF的不同是,它其实是一个跨多领域的系统,包括了单机上的容器引擎,容器管理系统,容器管理系统对外提供API,其他系统或用户通过这个来管理容器。这个系统发展过程也是各种犯错,犯错的主要原因也是因为领域掌握不深。在做T4的日子里,学会到的最重要的是怎么去设计这种跨多个专业领域的系统,怎么更好的划分模块的职责,设计交互逻辑,这段经历对我自己更为重要的意义是我有了做更大一些系统的架构的信心。

第三段经历,是做阿里电商的异地多活。这对我来说是真正的去做一个巨大系统的架构师,尽管我以前做HSF的时候参与了淘宝电商2.0-3.0的重大技术改造,但参与和自己主导是有很大区别的,这个架构改造涉及到了阿里电商众多不同专业领域的技术团队。在这个阶段,我学会的最主要的:

1). 子系统职责划分。在这种超大的技术方案中,很容易出现某些部分的职责重叠和冲突,这个时候怎么去划分子系统,就非常重要了。作为大架构师,这个时候要从团队的职责、团队的可持续性上去选择团队;

2). 大架构师最主要的职责是控制系统风险。对于这种超大系统,一定是多个专业领域的架构师和大架构师共同设计,怎么确保在执行的过程中对于系统而言最重要的风险能够被控制住,这是我真正的理解什么叫系统设计文档里设计原则的部分。

设计原则我自己觉得就是用来确保各个子系统在设计时都会遵循和考虑的,一定不能是虚的东西,例如在异地多活架构里,最重要的是如何控制数据风险,这个需要在原则里写上,最基本的原则是可接受系统不可用,但也要保障数据一致,而我看过更多的系统设计里设计原则只是写写的,或者千篇一律的,设计原则切实的体现了架构师对目标的理解(例如当时异地多活这个其实开始只是个概念,但做到什么程度才叫做到异地多活,这是需要解读的,也要确保在技术层面的设计上是达到了目标的),技术方案层面上的选择原则,并确保在细节的设计方案里有对于设计原则的承接以及执行;

3). 考虑问题的全面性。像异地多活这种大架构改造,涉及业务层面、各种基础技术层面、基础设施层面,对于执行节奏的决定要综合考虑人力投入、机器成本、基础设施布局诉求、稳定性控制等,这会比只是做一个小的系统的设计复杂非常多。

系统设计能力的成长,我自己觉得最重要的一是先在一两个技术领域做到专业,然后尽量扩大自己的知识广度。例如除了自己的代码部分外,还应该知道具体是怎么部署的,部署到哪去了,部署的环境具体是怎么样的,和整个系统的关系是什么样的。

像我自己,是在加入基础设施团队后才更加明白有些时候软件上做的一个决策,会导致基础设施上巨大的硬件、网络或机房的投入,但其实有可能只需要在软件上做些调整就可以避免,做做研发、做做运维可能是比较好的把知识广度扩大的方法。

第二点是练习自己做tradeoff的能力,这个比较难,做tradeoff这事需要综合各种因素做选择,但这也是所有的架构师最关键的,可以回头反思下自己在做各种系统设计时做出的tradeoff是什么。这个最好是亲身经历,听一些有经验的架构师分享他们选择背后的逻辑也会很有帮助,尤其是如果恰好你也在同样的挑战阶段,光听最终的架构结果其实大多数时候帮助有限。

技术Leader我觉得最好是能在架构师的基础上,后续注重成长的方面还是有挺大差别,就不在这篇里写了,后面再专门来写一篇。

程序员金字塔

我认为程序员的价值关键体现在作品上,被打上作品标签是一种很大的荣幸,作品影响程度的大小我觉得决定了金字塔的层次,所以我会这么去理解程序员的金字塔。

当然,要打造一款作品,仅有上面的两点能力是不够的,作品里很重要的一点是对业务、技术趋势的判断。

希望作为程序员的大伙,都能有机会打造一款世界级的作品,去为技术圈的发展做出贡献。

由于目前IT技术更新速度还是很快的,程序员这个行当是特别需要学习能力的。我一直认为,只有对程序员这个职业真正的充满兴趣,保持自驱,才有可能在这个职业上做好,否则的话是很容易淘汰的。

作者简介:

毕玄,2007年加入阿里,十多年来主要从事在软件基础设施领域,先后负责阿里的服务框架、Hbase、Sigma、异地多活等重大的基础技术产品和整体架构改造。



本文作者:云效鼓励师

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

查看原文

赞 84 收藏 58 评论 1

认证与成就

  • 获得 218 次点赞
  • 获得 37 枚徽章 获得 1 枚金徽章, 获得 11 枚银徽章, 获得 25 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-02-20
个人主页被 3.3k 人浏览