2024年7月

1. 每个人都应该拥抱AI

随着AI的飞速发展,把我们带到了一个全新的时代。在这个时代,AI将逐步渗透到各个方面,比如:自动驾驶、智能家居、医疗诊断、大模型等等。每个人都应该积极拥抱AI,让AI给我们提效。

2. AI不是取代人类,而是辅助人类

许多人担心AI会取代人类的工作,非常焦虑,事实上,AI的真正定位是作为人类的辅助工具,帮助我们更高效地完成工作。

我们可以把AI看作是一个“副驾驶”,它能够帮助我们处理繁琐的任务,提供有价值的思路,从而可以释放我们有更多的时间和精力去处理创造性的任务。

拿设计师这个职业来说,虽说AI可以绘画、出海报,但是设计出来的东西肯定不是百分百符合要修,还需要继续修改。非设计师会修改吗?非设计师能看出不合理的地方吗?

说句实话,我是不懂的,所以,就算是AI生成了一系列图片,还需要设计师继续微调,AI也只是给设计师赋能了,让他更高效。

再比如说程序员,虽说很多代码可以自动生成,但是非程序员了解整个软件工程体系吗?非程序员如何知道写的代码是否正确呢?

我想还得是程序员这个只能去使用代码生成工具吧。AI也只是给程序员赋能,让他更搞笑。

所以说AI还是在辅助本行业的人,给行业在提效。无需焦虑,积极拥抱就是了。

3. 最重要的是创新力和执行力

在AI时代,创新力和执行力,是最重要的。

首先说创造力,创新力是基于我们对各行各业的理解,产生的各种对应用场景想法,想通过想法去改善一些事情。

那执行力则是指我们能够快速地将这些创新转化为现实的能力。在这个快速变化的时代和人人都被提效的时代,拼的就是速度。

只有具备强大的创新力和执行力,我们才能抓住机遇,走在时代的前沿。

4. 不同的人聚焦于AI不同的层面

我们把整个AI体系分成:技术底层、技术中层、技术上层。对应的人群分为:底层研究人员、中层技术人员、上层应用人员。

在AI领域,不同的人可以根据自己的背景和兴趣选择不同的方向进行深耕:

  1. 底层研究人员
    :负责算法和模型的研究,推动AI技术的基础科学进步。
  2. 中层技术人员
    :专注于大模型或者小模型的应用开发,确保AI技术能够有效地应用于实际场景。
  3. 上层应用人员
    :关注如何使用各种AI工具来完成工作,提升工作效率和创造价值。

4.1. 底层研究人员

底层研究人员的任务是开发和改进AI算法和模型。他们通常拥有深厚的数学和计算机科学背景,能够进行高难度的科研工作。他们的工作为AI技术的发展奠定了坚实的基础。

具体的深耕方向包括:

  • 算法创新
    :研究新型算法,如深度学习、强化学习等,提升AI的计算效率和准确性。
  • 模型优化
    :开发更高效的模型结构,减少计算资源的消耗,提高模型的性能。
  • 基础理论
    :探索AI的基础理论问题,为未来的技术突破提供理论支持。

4.2. 中层技术人员

中层技术人员则专注于如何将大模型或者小模型应用到具体的场景中。他们需要具备良好的编程能力和系统设计能力,能够将科研成果转化为可用的产品和服务。

具体的深耕方向包括:

  • 应用开发
    :开发基于大模型的应用,如聊天机器人、智能客服等。
  • 系统集成
    :将AI技术与现有系统集成,提升整体系统的智能化水平。
  • 数据处理
    :处理和管理大规模数据,为AI模型提供高质量的训练数据。

4.3. 上层应用人员

上层应用人员则是普通用户和行业业务专家,他们的任务是找到AI技术的最佳应用场景,利用AI来提升工作效率和创造价值。在这一层,我们可以细化很多群体,拿自媒体人群举例:

自媒体人群可以利用AI技术来生成高质量的内容,如文章、图片、视频等,提高内容生产效率和质量。

  • 内容生成
    :利用AI生成高质量的文章、视频脚本等,节省时间和精力。
  • 数据分析
    :通过AI分析用户行为数据,优化内容策略,提高粉丝粘性。
  • 自动化营销
    :通过AI实现营销活动的自动化,如邮件营销、社交媒体营销等。

5. AI时代普通人的机会:AI应用层

对于普通人来说,AI应用层是最能带来机会的领域。

底层的技术研究只有科研人员和大公司才能开展,而应用层则是每个人都可以参与的。

就像当年互联网爆发时,大家都在搞“互联网+”,现在我们应该聚焦于“AI+”,将AI技术应用到各行各业,寻找新的商业机会。

在AI应用层,
AI Agent
是未来开发者发力的方向。开发者可以利用编程能力和系统设计能力打造更多更好的AI应用层产品。将AI Agent结合行业业务,创建出更多好用的应用。

例如,通过
AI Agent
,可以开发智能客服系统、自动化营销工具、行业特定的智能分析平台等。这些应用不仅能够提高效率,还能为用户提供更个性化和专业化的服务。

6. 总结

总的来说,在AI时代,我们每个人都应该积极拥抱AI技术,找到自己在AI生态系统中的位置。无论是进行基础研究、开发应用,还是利用AI提升工作效率,每个人都能在这个时代找到自己的价值。

通过聚焦于AI应用层,我们能够抓住时代的机遇,让AI赋能每个行业,实现更大的价值。

=====>>>>>>
关于我
<<<<<<=====

本篇完结!欢迎点赞 关注 收藏!!!

原文链接:
https://mp.weixin.qq.com/s/tOxRSGKEzDu67tnKVjnrNQ

问题

MGR 中,新节点在加入时,为了与组内其它节点的数据保持一致,它会首先经历一个分布式恢复阶段。在这个阶段,新节点会随机选择组内一个节点(Donor)来同步差异数据。

在 MySQL 8.0.17 之前,同步的方式只有一种,即基于 Binlog 的异步复制,这种方式适用于差异数据较少或需要的 Binlog 都存在的场景。

从 MySQL 8.0.17 开始,新增了一种同步方式-克隆插件,克隆插件可用来进行物理备份恢复,这种方式适用于差异数据较多或需要的 Binlog 已被 purge 的场景。

克隆插件虽然极大提升了恢复的效率,但备份毕竟是一个 IO 密集型的操作,很容易影响备份实例的性能,所以,我们一般不希望克隆操作在 Primary 节点上执行。

但 Donor 的选择是随机的(后面会证明这一点),有没有办法让 MGR 不从 Primary 节点克隆数据呢?

本文主要包括以下几部分:

  1. MGR 是如何执行克隆操作的?
  2. 可以通过 clone_valid_donor_list 设置 Donor 么?
  3. MGR 是如何选择 Donor 的?
  4. MGR 克隆操作的实现逻辑。
  5. group_replication_advertise_recovery_endpoints 的生效时机。

MGR 是如何执行克隆操作的?

起初还以为 MGR 执行克隆操作是调用克隆插件的一些内部接口。但实际上,MGR 调用的就是
CLONE INSTANCE
命令。

// plugin/group_replication/src/sql_service/sql_service_command.cc
long Sql_service_commands::internal_clone_server(
    Sql_service_interface *sql_interface, void *var_args) {
  ...
  std::string query = "CLONE INSTANCE FROM \'";
  query.append(q_user);
  query.append("\'@\'");
  query.append(q_hostname);
  query.append("\':");
  query.append(std::get<1>(*variable_args));
  query.append(" IDENTIFIED BY \'");
  query.append(q_password);
  bool use_ssl = std::get<4>(*variable_args);
  if (use_ssl)
    query.append("\' REQUIRE SSL;");
  else
    query.append("\' REQUIRE NO SSL;");

  Sql_resultset rset;
  long srv_err = sql_interface->execute_query(query, &rset);
  ...
}

既然调用的是 CLONE INSTANCE 命令,那是不是就可以通过 clone_valid_donor_list 参数来设置 Donor(被克隆实例)呢?

可以通过 clone_valid_donor_list 设置Donor么

不能。

在获取到 Donor 的 endpoint(端点,由 hostname 和 port 组成)后,MGR 会通过
update_donor_list
函数设置 clone_valid_donor_list。

clone_valid_donor_list 的值即为 Donor 的 endpoint。

所以,在启动组复制之前,在 mysql 客户端中显式设置 clone_valid_donor_list 是没有效果的。

// plugin/group_replication/src/plugin_handlers/remote_clone_handler.cc
int Remote_clone_handler::update_donor_list(
    Sql_service_command_interface *sql_command_interface, std::string &hostname,
    std::string &port) {
  std::string donor_list_query = " SET GLOBAL clone_valid_donor_list = \'";
  plugin_escape_string(hostname);
  donor_list_query.append(hostname);
  donor_list_query.append(":");
  donor_list_query.append(port);
  donor_list_query.append("\'");
  std::string error_msg;
  if (sql_command_interface->execute_query(donor_list_query, error_msg)) {
      ...
  }
  return 0;
}

既然是先有 Donor,然后才会设置 clone_valid_donor_list,接下来我们看看 MGR 是如何选择 Donor 的?

MGR 是如何选择 Donor 的?

MGR 选择 Donor 可分为以下两步:

  1. 首先,判断哪些节点适合当 Donor。满足条件的节点会放到一个动态数组(m_suitable_donors)中, 这个操作是在
    Remote_clone_handler::get_clone_donors
    函数中实现的。
  2. 其次,循环遍历 m_suitable_donors 中的节点作为 Donor。如果第一个节点执行克隆操作失败,则会选择第二个节点,依次类推。

下面,我们看看
Remote_clone_handler::get_clone_donors
的实现细节。

void Remote_clone_handler::get_clone_donors(
    std::list<Group_member_info *> &suitable_donors) {
  // 获取集群所有节点的信息
  Group_member_info_list *all_members_info =
      group_member_mgr->get_all_members();
  if (all_members_info->size() > 1) {
    // 这里将原来的 all_members_info 打乱了,从这里可以看到 donor 是随机选择的。
    vector_random_shuffle(all_members_info);
  }

  for (Group_member_info *member : *all_members_info) {
    std::string m_uuid = member->get_uuid();
    bool is_online =
        member->get_recovery_status() == Group_member_info::MEMBER_ONLINE;
    bool not_self = m_uuid.compare(local_member_info->get_uuid());
    // 注意,这里只是比较了版本
    bool supports_clone =
        member->get_member_version().get_version() >=
            CLONE_GR_SUPPORT_VERSION &&
        member->get_member_version().get_version() ==
            local_member_info->get_member_version().get_version();

    if (is_online && not_self && supports_clone) {
      suitable_donors.push_back(member);
    } else {
      delete member;
    }
  }

  delete all_members_info;
}

该函数的处理流程如下:

  1. 获取集群所有节点的信息,存储到 all_members_info 中。

    all_members_info 是个动态数组,数组中的元素是按照节点 server_uuid 从小到大的顺序依次存储的。

  2. 通过
    vector_random_shuffle
    函数将 all_members_info 进行随机重排。

  3. 选择 ONLINE 状态且版本大于等于 8.0.17 的节点添加到 suitable_donors 中。

    为什么是 8.0.17 呢,因为克隆插件是 MySQL 8.0.17 引入的。

    注意,这里只是比较了版本,没有判断克隆插件是否真正加载。

函数中的 suitable_donors 实际上就是 m_suitable_donors。

get_clone_donors(m_suitable_donors);

基于前面的分析,可以看到,在 MGR 中,作为被克隆节点的 Donor 是随机选择的。

既然 Donor 的选择是随机的,想不从 Primary 节点克隆数据似乎是实现不了的。

分析到这里,问题似乎是无解了。

别急,接下来让我们分析下 MGR 克隆操作的实现逻辑。

MGR 克隆操作的实现逻辑

MGR 克隆操作是在
Remote_clone_handler::clone_thread_handle
函数中实现的。

// plugin/group_replication/src/plugin_handlers/remote_clone_handler.cc
[[noreturn]] void Remote_clone_handler::clone_thread_handle() {
  ...
  while (!empty_donor_list && !m_being_terminated) {
    stage_handler.set_completed_work(number_attempts);
    number_attempts++;

    std::string hostname("");
    std::string port("");
    std::vector<std::pair<std::string, uint>> endpoints;

    mysql_mutex_lock(&m_donor_list_lock);
    // m_suitable_donors 是所有符合 Donor 条件的节点
    empty_donor_list = m_suitable_donors.empty();
    if (!empty_donor_list) {
      // 获取数组中的第一个元素
      Group_member_info *member = m_suitable_donors.front();
      Donor_recovery_endpoints donor_endpoints;
      // 获取 Donor 的端点信息 
      endpoints = donor_endpoints.get_endpoints(member);
      ...
      // 从数组中移除第一个元素
      m_suitable_donors.pop_front();
      delete member;
      empty_donor_list = m_suitable_donors.empty();
      number_servers = m_suitable_donors.size();
    }
    mysql_mutex_unlock(&m_donor_list_lock);

    // No valid donor in the list
    if (endpoints.size() == 0) {
      error = 1;
      continue;
    }
    // 循环遍历 endpoints 中的每个端点
    for (auto endpoint : endpoints) {
      hostname.assign(endpoint.first);
      port.assign(std::to_string(endpoint.second));

      // 设置 clone_valid_donor_list
      if ((error = update_donor_list(sql_command_interface, hostname, port))) {
        continue; /* purecov: inspected */
      }

      if (m_being_terminated) goto thd_end;

      terminate_wait_on_start_process(WAIT_ON_START_PROCESS_ABORT_ON_CLONE);
      // 执行克隆操作
      error = run_clone_query(sql_command_interface, hostname, port, username,
                              password, use_ssl);

      // Even on critical errors we continue as another clone can fix the issue
      if (!critical_error) critical_error = evaluate_error_code(error);

      // On ER_RESTART_SERVER_FAILED it makes no sense to retry
      if (error == ER_RESTART_SERVER_FAILED) goto thd_end;

      if (error && !m_being_terminated) {
        if (evaluate_server_connection(sql_command_interface)) {
          critical_error = true;
          goto thd_end;
        }

        if (group_member_mgr->get_number_of_members() == 1) {
          critical_error = true;
          goto thd_end;
        }
      }

      // 如果失败,则选择下一个端点进行重试。
      if (!error) break;
    }

    // 如果失败,则选择下一个 Donor 进行重试。
    if (!error) break;
  }
...
}

该函数的处理流程如下:

  1. 首先会选择一个 Donor。可以看到,代码中是通过
    front()
    函数来获取 m_suitable_donors 中的第一个元素。
  2. 获取 Donor 的端点信息。
  3. 循环遍历 endpoints 中的每个端点。
  4. 设置 clone_valid_donor_list。
  5. 执行克隆操作。如果操作失败,则会进行重试,首先是选择下一个端点进行重试。如果所有端点都遍历完了,还是没有成功,则会选择下一个 Donor 进行重试,直到遍历完所有 Donor。

当然,重试是有条件的,出现以下情况就不会进行重试:

  1. error == ER_RESTART_SERVER_FAILED
    :实例重启失败。

    实例重启是克隆操作的最后一步,之前的步骤依次是:1. 获取备份锁。2. DROP 用户表空间。3. 从 Donor 实例拷贝数据。

    既然数据都已经拷贝完了,就没有必要进行重试了。

  2. 执行克隆操作的连接被 KILL 了且重建失败。

  3. group_member_mgr->get_number_of_members() == 1
    :集群只有一个节点。

既然克隆操作失败了会进行重试,那么思路来了,如果不想克隆操作在 Primary 节点上执行,很简单,让 Primary 节点上的克隆操作失败了就行。

怎么让它失败呢?

一个克隆操作,如果要在 Donor(被克隆节点)上成功执行,Donor 需满足以下条件:

  1. 安装克隆插件。
  2. 克隆用户需要 BACKUP_ADMIN 权限。

所以,如果要让克隆操作失败,任意一个条件不满足即可。推荐第一个,即不安装或者卸载克隆插件。

为什么不推荐回收权限这种方式呢?

因为卸载克隆插件这个操作(
uninstall plugin clone
)不会记录 Binlog,而回收权限会。

虽然回收权限的操作也可以通过
SET SQL_LOG_BIN=0
的方式不记录 Binlog,但这样又会导致集群各节点的数据不一致。所以,非常不推荐回收权限这种方式。

所以,如果不想 MGR 从 Primary 节点克隆数据,直接卸载 Primary 节点的克隆插件即可。

问题虽然解决了,但还是有一个疑问:endpoints 中为什么会有多个端点呢?不应该就是 Donor 的实例地址,只有一个么?这个实际上与 group_replication_advertise_recovery_endpoints 有关。

group_replication_advertise_recovery_endpoints

group_replication_advertise_recovery_endpoints 参数是 MySQL 8.0.21 引入的,用来自定义恢复地址。

看下面这个示例。

group_replication_advertise_recovery_endpoints= "127.0.0.1:3306,127.0.0.1:4567,[::1]:3306,localhost:3306"

在设置时,要求端口必须来自 port、report_port 或者 admin_port。

而主机名只要是服务器上的有效地址即可(一台服务器上可能存在多张网卡,对应的会有多个 IP),无需在 bind_address 或 admin_address 中指定。

除此之外,如果要通过 admin_port 进行分布式恢复操作,用户还需要授予 SERVICE_CONNECTION_ADMIN 权限。

下面我们看看 group_replication_advertise_recovery_endpoints 的生效时机。

在选择完 Donor 后,MGR 会调用
get_endpoints
来获取这个 Donor 的 endpoints。

// plugin/group_replication/src/plugin_variables/recovery_endpoints.cc
Donor_recovery_endpoints::get_endpoints(Group_member_info *donor) {
  ...
  std::vector<std::pair<std::string, uint>> endpoints;
  // donor->get_recovery_endpoints().c_str() 即 group_replication_advertise_recovery_endpoints 的值
  if (strcmp(donor->get_recovery_endpoints().c_str(), "DEFAULT") == 0) {
    error = Recovery_endpoints::enum_status::OK;
    endpoints.push_back(
        std::pair<std::string, uint>{donor->get_hostname(), donor->get_port()});
  } else {
    std::tie(error, err_string) =
        check(donor->get_recovery_endpoints().c_str());
    if (error == Recovery_endpoints::enum_status::OK)
      endpoints = Recovery_endpoints::get_endpoints();
  }
  ...
  return endpoints;
}

如果 group_replication_advertise_recovery_endpoints 为 DEFAULT(默认值),则会将 Donor 的 hostname 和 port 设置为 endpoint。

注意,节点的 hostname、port 实际上就是 performance_schema.replication_group_members 中的 MEMBER_HOST、 MEMBER_PORT。

hostname 和 port 的取值逻辑如下:

// sql/rpl_group_replication.cc
void get_server_parameters(char **hostname, uint *port, char **uuid,
                           unsigned int *out_server_version,
                           uint *out_admin_port) {
  ...
  if (report_host)
    *hostname = report_host;
  else
    *hostname = glob_hostname;

  if (report_port)
    *port = report_port;
  else
    *port = mysqld_port;
  ...
  return;
}

优先使用 report_host、report_port,其次才是主机名、mysqld 的端口。

如果 group_replication_advertise_recovery_endpoints 不为 DEFAULT,则会该参数的值设置为 endpoints。

所以,一个节点,只有被选择为 Donor,设置的 group_replication_advertise_recovery_endpoints 才会有效果。

而节点有没有设置 group_replication_advertise_recovery_endpoints 与它能否被选择为 Donor 没有任何关系。

总结

  1. MGR 选择 Donor 是随机的。
  2. MGR 在执行克隆操作之前,会将 clone_valid_donor_list 设置为 Donor 的 endpoint,所以,在启动组复制之前,在 mysql 客户端中显式设置 clone_valid_donor_list 是没有效果的。
  3. MGR 执行克隆操作,实际上调用的就是
    CLONE INSTANCE
    命令。
  4. performance_schema.replication_group_members 中的 MEMBER_HOST 和 MEMBER_PORT,优先使用 report_host、report_port,其次才是主机名、mysqld 的端口。
  5. 一个节点,只有被选择为 Donor,设置的 group_replication_advertise_recovery_endpoints 才会有效果。
  6. 如果不想 MGR 从 Primary 节点克隆数据,直接卸载 Primary 节点的克隆插件即可。

延伸阅读

  1. MySQL 8.0 新特性之 Clone Plugin
  2. 《MySQL实战》组复制章节

大家好,我是
晓凡

写在前面

如果你是一名Java开发者,下面说到的这些开源项目应该不陌生了。在实际工作中或多或少都用到过。

趁着周末,特地整理下阿里巴巴旗下开源的10款顶级项目

TOP10 rocketmq

① 简介

RocketMQ
是一个分布式消息中间件。专门负责在不同的软件系统之间传递消息

最初由阿里巴巴开发并在2012年开源,在2017年,
RocketMQ
成为了 Apache 软件基金会的顶级项目。

RocketMQ
不仅在阿里巴巴内部广泛使用,也被许多其他公司采用,适用于各种场景,如金融交易、电商订单处理、日志聚合、流数据处理等

② 开源地址

https://github.com/apache/rocketmq

③ 目前stars数

20.8k

④ 特点,为了能直观说明其特点,这里我们以快递小哥为例来说明

  1. 速度快
    : 就像快递小哥不再骑自行车送快递,而是骑的电动车,
    RocketMQ
    处理消息的速度非常快,可以同时处理很多消息。
  2. 靠谱
    : 即使遇到刮风下雨,快递小哥也会想尽办法把快递送到。
    RocketMQ
    也有类似的能力,即使系统出了点问题,它也能确保消息不会丢失,可靠地送达。
  3. 灵活
    : 快递小哥可以根据不同的需求,选择不同的路线和方式送快递。
    RocketMQ
    也可以根据需要,灵活地部署和配置,适应不同的使用场景。
  4. 能存很多东西
    : 快递仓库可以放很多快递包裹。
    RocketMQ
    也能存储大量的信息,即使一下子来了很多消息,它也能先存起来,慢慢地发送。
  5. 有序
    : 快递小哥送快递时,相同地址的快递会放在一起送。
    RocketMQ
    也能让相同业务的消息按照顺序一个接一个地处理。
  6. 延时服务
    : 有时候我们会选择定时派送服务,比如让快递明天再送。
    RocketMQ
    也支持这种延时发送消息的功能。
  7. 过滤服务
    : 快递小哥在送快递时,会根据收件人的要求只送特定的快递。
    RocketMQ
    也允许消费者只接收他们感兴趣的消息。
  8. 处理复杂情况
    : 如果快递在途中遇到问题,快递公司会有一套办法来处理。
    RocketMQ
    也有类似的机制,比如消息发送失败了可以重试,或者把消息放到一个特殊的地方等待处理。
  9. 容易监控
    : 快递公司会有系统来监控快递的状态。
    RocketMQ
    也有这样的监控系统,可以实时查看消息处理的状态和性能。

TOP9 Sentinel

① 简介

Sentinel 是阿里巴巴开源的一个流量控制框架,就像餐厅或者旅游景点控制流量的“门卫”一样,专门用来保护你的软件服务不被过多的请求压垮

② 开源地址

https://github.com/alibaba/Sentinel

③ 目前stars数

22.2k

④ 特点

想象一下,你开了一家餐厅,生意特别火,每天都有很多人来吃饭。但是,餐厅的接待能力是有限的,不可能一下子接待所有的顾客。这时候,你需要一个“门卫”,来控制进店的顾客数量,保证餐厅的服务质量不受影响。这个“门卫”,在软件世界里,就是
Sentinel

它主要有以下几个作用:

  1. 流量控制
    :就像餐厅的门卫控制进店的顾客数量一样,
    Sentinel
    可以控制进入你的软件服务的请求数量。如果请求太多,它就会拒绝一些请求,避免系统崩溃。
  2. 熔断机制
    :如果你的餐厅厨房突然出了问题,门卫会告诉顾客暂时不要进店,等厨房修好了再继续营业。
    Sentinel
    也有类似的功能,当系统出现问题时,它可以暂时停止服务,避免问题进一步扩大。
  3. 系统保护
    :餐厅的门卫不仅要控制人数,还要保护餐厅的安全。
    Sentinel
    也可以监控系统的运行状态,一旦发现系统负载过高,就会采取措施保护系统,比如减少请求量。
  4. 灵活配置
    :餐厅的门卫可以根据实际情况调整接待策略,比如节假日人多就多接待一些。
    Sentinel
    也支持灵活配置,你可以根据需要调整流量控制的策略。
  5. 实时监控
    :餐厅的门卫需要随时了解店内的情况,
    Sentinel
    也有实时监控的功能,可以实时查看系统的运行状态和流量情况。
  6. 易于集成
    :就像餐厅的门卫不需要顾客做太多配合一样,
    Sentinel
    也很容易集成到现有的系统中,不需要做太多的改动。

总之,
Sentinel
就像是一个智能的“门卫”,帮助你管理软件服务的流量,保证系统的稳定运行。

功能概述
如下图所示

功能概述

TOP8 fastjson

① 简介

fastjson
是一个由阿里巴巴开发并开源的高性能
JSON
处理库,主要用于 Java 应用程序。

它的设计目标是在保证速度的同时简化
JSON
数据的序列化和反序列化过程

② 开源地址

https://github.com/alibaba/fastjson

③目前stars数

25.7k

④ 特点

  1. 高性能

    • FastJSON
      通过优化算法和减少反射调用等方式,实现了非常快的
      JSON
      序列化和反序列化速度。
    • 它在处理大量数据时表现出色,尤其适合高并发和大数据量的场景。
  2. 轻量级

    • FastJSON
      没有外部依赖,可以直接在任何使用
      JDK
      的环境中运行,无需添加额外的
      jar
      文件。
  3. 功能丰富

    • 支持 Java 中常见的数据类型,包括基本类型、
      JavaBeans
      、集合、
      Map
      、日期、枚举和泛型等。
    • 支持
      JSON
      格式的数据处理,包括解析、生成和修改
      JSON
      字符串。
  4. 安全性

    • FastJSON
      在安全方面持续改进,修复了多个潜在的安全漏洞,以防止反序列化攻击等安全风险。
  5. 易用性

    • 提供了简洁的
      API
      接口,使得
      JSON
      数据的处理变得简单直观。
    • 支持自定义序列化器和反序列化器,以便处理复杂的数据结构或特殊需求。
  6. 开源许可

    • 使用
      Apache License 2.0
      许可证发布,这意味着它是完全免费的,可以用于商业项目。

fastjson
的评价,网络上可是褒贬不一,曾经爆出过存在安全漏洞及bug

处于这些原因,作者也是推出了
FastJson2
,在
FastJson
上做了重大版本更新,旨在解决之前提出的安全性问题及bug修复,

同时还增加了一些新特性及性能也得到了提升

下面是
FastJson2
开源地址

https://github.com/alibaba/fastjson2

目前已有
3.6k

TOP7 spring-cloud-alibaba

① 简介

spring-cloud-alibaba
是一套基于
Spring Cloud
和阿里巴巴中间件的微服务开发解决方案。

② 开源地址

https://github.com/alibaba/spring-cloud-alibaba

③目前stars数

27.6k

④ 特点

想象一下,你是一个餐厅老板,你的餐厅需要提供各种服务,比如点餐、支付、送外卖等。

但是,这些服务不是一下子就能搞定的,你需要很多工具和帮手。

Spring Cloud Alibaba
就像是你开餐厅时的“一站式服务超市”,提供了你需要的所有工具和帮手。

  1. 一站式服务
    : 提供了一整套工具和框架,帮助你轻松搭建和管理分布式系统。就像你在一个超市里可以买到所有开餐厅需要的东西一样。
  2. 分布式系统
    :如果你的餐厅有很多分店,每个分店都需要独立运作,但又需要互相协作。
    Spring Cloud Alibaba
    可以帮助你管理这些分店,确保它们能够协同工作。
  3. 服务发现
    :就像你需要知道每个分店的位置一样,
    Spring Cloud Alibaba
    有一个功能叫做“服务发现”,可以帮你找到系统中的各个服务组件。
  4. 配置管理
    :餐厅的菜单和价格可能会变,
    Spring Cloud Alibaba
    可以帮你管理这些变化,确保所有分店的菜单和价格都是最新的。
  5. 负载均衡
    :餐厅的客流量大时,需要合理分配顾客到不同的服务员那里。
    Spring Cloud Alibaba
    也有类似的功能,可以帮你合理分配请求,避免某个服务组件过载。
  6. 断路器
    :就像餐厅在遇到问题时会暂时停止服务一样,
    Spring Cloud Alibaba
    提供了“断路器”功能,当某个服务组件出现问题时,可以暂时切断它的服务,避免影响整个系统。
  7. 消息队列
    :餐厅在处理订单时,可能需要传递很多信息。
    Spring Cloud Alibaba
    支持消息队列,帮助你在不同的服务组件之间传递消息。
  8. 易于集成
    :就像超市里的商品可以直接拿回家用一样,
    Spring Cloud Alibaba
    也很容易集成到你的系统中,不需要太多的改动。

总之,
Spring Cloud Alibaba
就像是你开餐厅时的“万能工具箱”,提供了你需要的所有工具和服务,帮助你轻松管理复杂的分布式系统。

TOP6 Druid

① 简介

Druid 是阿里巴巴开源的一款高性能的 Java 数据库连接池和
SQL
监控工具

② 开源地址

https://github.com/alibaba/druid

③目前stars数

27.8k

④ 特点

Druid
就像是你家里的水管系统。想象一下,如果你住在一个很大的房子里,有很多水龙头,你需要一个系统来确保每个水龙头都能正常供水,而且水压要合适,水管还要结实耐用。

  1. 高效

    Druid
    特别高效,就像你家里的水管系统,水能快速流到每一个水龙头,
    Druid
    也能快速处理数据库的请求。

  2. 监控
    :水管系统如果漏水或者堵塞,你会很头疼。
    Druid
    可以监控数据库的访问情况,就像你检查水管有没有问题一样,让你及时发现并解决问题。

    sql监控

  3. 连接池

    Druid
    提供了一个连接池,这就像是你家里有很多备用水管,需要用水的时候,直接拿一个来用,用完再放回去,不用每次都去新买一根水管。

  4. 安全性
    :水管如果随便接,可能会漏水或者影响其他水龙头的使用。
    Druid
    可以加密数据库的密码,保护你的数据不被外人随便访问。

  5. 灵活性
    :水管系统需要适应不同的情况,比如有时候需要分流,有时候需要增压。
    Druid
    也很灵活,可以根据不同的数据库操作需求进行调整。

  6. SQL
    日志

    :就像你会记录家里每个月的用水量,
    Druid
    可以记录执行的
    SQL
    语句,帮助你分析和优化数据库的使用。

  7. 易用性
    :安装和使用
    Druid
    就像安装家里的水管系统一样,不需要太复杂的操作,按照说明一步步来就行。

总之,
Druid
就像是一个高级的水管系统,不仅保证你的数据库操作高效、安全,还能帮你监控和管理数据流动,让数据库的使用更加顺畅和可靠。

TOP5 canal

① 简介

Canal
是阿里巴巴开源的一个数据同步工具,它的主要作用是“监控”和“同步”数据库的变化

② 开源地址

https://github.com/alibaba/canal

③目前stars数

28.1k

④ 特点

想象一下,你有一个大鱼塘,里面养了很多鱼。

但是,你想知道鱼塘里到底发生了什么,比如哪些鱼长大了,哪些鱼生病了,或者有没有新鱼进来。

这时候,你需要一个“水下观察站”,帮你监控鱼塘里的情况。


Canal
就充当了“水下观察站”的角色

  1. 数据变化监控
    :就像你用观察站监控鱼塘,
    Canal
    监控数据库的变化,比如数据的增加、删除、修改等。

  2. 实时同步
    :如果鱼塘里发生了新情况,你肯定希望立刻知道。

    Canal
    可以实时地把数据库的变化同步到别的地方,比如另一个数据库或者消息队列。

  3. 增量订阅
    :你不需要知道鱼塘里所有鱼的情况,可能只关心新进来的鱼或者生病的鱼。

    Canal
    支持增量订阅,只同步你关心的数据变化。

  4. 易于扩展
    :如果你的鱼塘越来越大,一个观察站可能不够用。

    Canal
    很容易扩展,可以增加更多的“观察站”来满足需求。

  5. 多种数据库支持
    :你的鱼塘可能有很多种类的鱼,
    Canal
    支持多种数据库,比如
    MySQL

    Oracle
    等。

  6. 轻量级
    :虽然功能强大,但
    Canal
    很轻量级,不会占用太多资源,就像一个小巧的观察站,不会影响鱼塘的生态。

  7. 易用性
    :搭建和使用
    Canal
    就像搭建一个观察站一样,按照说明一步步来,不需要太复杂的操作。

总之,
Canal
就像一个高效的“水下观察站”,帮你实时监控和同步数据库的变化,让你对数据流动的情况了如指掌。

TOP4 nacos

① 简介

Nacos
是一个服务发现与配置管理平台,旨在帮助开发者更轻松地构建、部署和管理云原生应用。

目前国内大部分公司都在使用

② 开源地址

https://github.com/alibaba/nacos

③目前stars数

29.6k

④ 特点

想象一下,你是一个大型购物中心的经理。

这个购物中心有很多店铺,每个店铺都有自己的营业时间和服务内容。

你需要一个系统来管理这些店铺,确保顾客能够找到他们需要的服务。

Nacos
像是你管理购物中心的“智能助手”。它主要做以下几件事:

  1. 服务发现
    :就像顾客需要知道哪些店铺开门了,
    Nacos
    可以帮助你的应用程序发现系统中的其他服务。比如,一个在线购物网站需要知道支付服务和物流服务是否可用。
  2. 服务配置
    :每个店铺可能有不同的服务标准和价格。
    Nacos
    可以管理这些配置信息,确保所有的服务都按照统一的标准运行。
  3. 服务管理
    :购物中心的店铺需要定期检查和维护。
    Nacos
    可以帮助你管理和维护系统中的服务,比如自动重启失败的服务或者扩展服务的容量。
  4. 流量管理
    :购物中心在节假日可能会迎来大量顾客。
    Nacos
    可以通过流量控制和负载均衡,确保所有的服务都能够平稳运行,不会因为突然增加的请求而崩溃。
  5. 服务健康检查
    :就像你需要定期检查店铺的卫生和服务质量,
    Nacos
    可以监控服务的健康状态,及时发现并处理问题。
  6. 易于集成
    :就像购物中心可以很容易地增加新的店铺,
    Nacos
    也很容易集成到现有的系统中,不需要做太多的改动。
  7. 跨语言支持
    :购物中心可能有不同的店铺类型,比如餐厅、服装店、电子产品店。
    Nacos
    支持多种编程语言,比如
    Java

    .NET

    Go
    等,适合各种类型的服务。

总之,
Nacos
就像是一个智能的购物中心管理系统,帮助你轻松管理复杂的微服务架构,确保所有的服务都能够高效、稳定地运行。

TOP3 easyexcel

① 简介

EasyExcel
是一个专门用于处理
Excel
文件。

Java解析、生成Excel比较有名的框架有
Apache poi

jxl
。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel
重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

② 开源地址

https://github.com/alibaba/easyexcel

③目前stars数

31.5k

④ 特点

  1. 低内存消耗

    EasyExcel
    通过流式读写 Excel 文件,不会一次性将整个文件加载到内存中,因此可以处理包含百万行数据的大文件,而不会导致内存溢出。

    16M内存23秒读取75M(46W行25列)的Excel

    内存占用

  2. 易用性


    • 提供了简洁的
      API
      ,使得读写 Excel 文件变得简单直观,减少了开发工作量。

⑤ 使用

easyexcel
的使用在之前的文章中介绍过,感兴趣的小伙伴可以看过来

传送门

TOP2 arthas

① 简介

一款强大的 Java 诊断工具,当遇到复杂或难以复现的故障时,
Arthas
绝对可以帮助到你

② 开源地址

https://github.com/alibaba/arthas

③目前stars数

35.1k

④ 特点

  1. 方法跟踪


    • 可以查看方法的执行轨迹,包括调用层级、耗时、参数和返回值,帮助理解方法的执行流程和性能瓶颈。
  2. 类和方法字节码修改


    • 允许在运行时修改类和方法的字节码,无需重新编译和部署应用,便于快速验证代码修改的效果。
  3. JVM 参数调整


    • 可以在线修改
      JVM
      参数,如垃圾回收策略、堆大小等,以优化应用性能或解决内存泄漏等问题。
  4. 线程分析


    • 提供了线程堆栈信息、线程状态和锁信息,帮助分析线程死锁和阻塞等问题。
  5. 内存泄漏检测


    • 可以分析对象的引用关系,查找可能的内存泄漏点。
  6. 代码热更新


    • 支持代码热更新,可以在不停机的情况下更新类的实现。
  7. 丰富的命令行工具


    • Arthas
      提供了诸如
      trace
      ,
      watch
      ,
      thread
      ,
      dashboard
      ,
      telnet
      等命令,涵盖了 Java 应用诊断的各个方面。
  8. 自动补全和历史记录


    • Arthas
      的命令行界面支持 Tab 键自动补全和命令历史记录,提高了使用效率。
  9. IDEA 插件


    • 提供了
      IntelliJ IDEA
      插件,可以更方便地在
      IDE
      内部使用
      Arthas
      进行诊断。

    idea中安装arthas

  10. 跨平台


    • Arthas
      可以在
      Linux

      macOS

      Windows
      等平台上运行,适用于各种开发和生产环境。

至于怎么使用,由于文章篇幅原因,这里就不展开了。如果小伙伴们感兴趣,可以在后面的文章中说一说。

TOP1 dubbo

① 简介

一个高性能的
Java RPC
框架,用来帮助不同的服务之间进行通信

并于 2008 年开源。2017 年,
Dubbo
正式成为
Apache
软件基金会的顶级项目。

② 开源地址

https://github.com/apache/dubbo

③目前stars数

40.3k

④ 特点

想象一下,你开了一家餐厅,生意非常火爆。

为了提高效率,你决定把一些工作外包出去,比如让附近的面包店帮你做面包,让附近的蔬菜供应商每天给你送新鲜的蔬菜。

但是,你和这些供应商之间的沟通需要一个系统来协调和管理,这就是
Dubbo
的作用。

  1. 远程调用
    :就像你给供应商打电话下订单,
    Dubbo
    允许你的程序远程调用其他服务的方法,就像是直接调用本地方法一样简单。
  2. 服务注册与发现
    :你的餐厅需要知道哪些供应商是可用的,
    Dubbo
    提供了一个服务注册中心,供应商(服务提供者)会在这里注册自己,你的餐厅(服务消费者)可以发现这些供应商。
  3. 负载均衡
    :假设你有多个面包供应商,
    Dubbo
    可以帮助你决定从哪个供应商那里采购面包,以平衡每个供应商的负载,避免某个供应商过载。
  4. 容错机制
    :如果某个供应商突然不能供货了,
    Dubbo
    可以帮助你的餐厅自动切换到其他供应商,确保餐厅的运营不受影响。
  5. 监控和管理

    Dubbo
    提供了丰富的监控和管理工具,让你可以实时了解各个服务的状态,比如响应时间、调用次数等。
  6. 扩展性
    :随着餐厅生意的扩展,你可能需要更多的供应商。
    Dubbo
    的设计使得扩展服务变得非常容易,你可以轻松地增加新的服务或者修改现有的服务。
  7. 协议支持

    Dubbo
    支持多种通信协议,比如
    Dubbo
    协议、
    RMI
    协议等,就像是你和供应商可以通过电话、邮件或者微信等多种方式沟通。
  8. 易于集成
    :就像是你可以很容易地和新的供应商建立合作关系,
    Dubbo
    也很容易集成到现有的 Java 应用中,不需要太多的改动。

总之,
Dubbo
就像是你餐厅的“供应链协调员”,帮助你高效、可靠地管理和协调与各个服务提供者的通信,确保你的餐厅能够顺利运营。

架构图如下

Dubbo架构图

本期内容到这儿就结束了
★,°
:.☆( ̄▽ ̄)/$:
.°★
。 希望对您有所帮助

我们下期再见 ヾ(•ω•`)o (●'◡'●)

七月的夏日,阳光如火,但小悦的心中却是一片清凉与激情。在数据分析项目组的新岗位上,她仿佛找到了自己新的舞台,这里让她得以将深厚的后端技术实力与数据分析的精髓深度融合。每天,她都沉浸在业务需求的分析与数据驱动的决策之中,与业务、产品等多部门紧密合作,共同揭开数据背后的秘密,为企业的发展贡献自己的力量。

正当她全身心投入到新环境的学习与探索中时,微信工作群的一则消息如同夏日里的一阵清风,为她带来了新的机遇。逸尘,作为某项目微信群中的甲方代表,通过该群发布了紧急的数据分析任务,而他当时并未意识到是小悦将会被指定来负责处理这项任务。虽然两人上周在咖啡馆的偶遇只是匆匆一瞥,但那次偶遇似乎为这次合作埋下了一丝伏笔。

面对这份突如其来的挑战,小悦没有丝毫的慌乱与犹豫。她深知,无论是熟悉的伙伴还是陌生的合作者,工作中的每一次挑战都是自我提升与成长的宝贵机会。于是,她迅速调整心态,仔细阅读任务要求,以专业的态度和严谨的精神回复了逸尘。


小悦接到的任务,是根据原始数据表分析每个公司内部不同部门的销售业绩,并计算了每个部门销售业绩的排名、与下一名销售业绩的差距、与本部门最高销售业绩的差距、以及与所有部门销售平均值的差异。

--根据需求建测试表
create tabletemp1 (
comp_name
varchar(100),
dept_name
varchar(100),
sales numeric(
18,4)
)
--写入测试数据: INSERT INTO temp1 VALUES ('CompanyA', 'Dept 1', 100);INSERT INTO temp1 VALUES ('CompanyA', 'Dept 3', 80);INSERT INTO temp1 VALUES ('CompanyA', 'Dept 4', 80);INSERT INTO temp1 VALUES ('CompanyA', 'Dept 2', 50);INSERT INTO temp1 VALUES ('CompanyB', 'Dept 4', 120);INSERT INTO temp1 VALUES ('CompanyB', 'Dept 3', 90);INSERT INTO temp1 VALUES ('CompanyB', 'Dept 2', 80);INSERT INTO temp1 VALUES ('CompanyB', 'Dept 1', 70);


面对分析公司内部不同部门销售业绩并计算排名、差距及异的需求,小悦的第一反应是使用临时表,方案一:

--创建临时表来存储每个公司的最大销售额和平均销售额
CREATETABLE temp_max_avg AS
SELECTcomp_name,MAX(sales) ASmax_sales,AVG(sales) ASavg_salesFROMtemp1GROUP BYcomp_name;--创建临时表来存储排名和前一个销售额
CREATETABLE temp_rank_lag AS
SELECTcomp_name,
dept_name,
sales,
ROW_NUMBER()
OVER (PARTITION BY comp_name ORDER BY sales DESC) ASrank,
LAG(sales)
OVER (PARTITION BY comp_name ORDER BY sales DESC) ASprev_salesFROMtemp1;--创建最终的临时表来存储所有结果 CREATETABLE temp_final AS SELECTt1.comp_name,
t1.dept_name,
t1.sales,
t2.rank,
COALESCE(t2.prev_sales - t1.sales, 0) ASnext_behind,
tma.max_sales
- t1.sales AStotal_behind,
t1.sales
- tma.avg_sales ASdiff_from_avgFROMtemp1 t1JOIN temp_rank_lag t2 ON t1.comp_name = t2.comp_name AND t1.sales =t2.salesJOIN temp_max_avg tma ON t1.comp_name =tma.comp_name;--查询最终结果 SELECT * FROMtemp_finalORDER BY comp_name, rank;

这段SQL查询主要用于分析每个公司内部不同部门的销售业绩,并计算了每个部门销售业绩的排名、与下一名销售业绩的差距、与本部门最高销售业绩的差距、以及与销售平均值的差异。以下是对各个字段的解释:

  • comp_name
    :公司名称
  • dept_name
    :部门名称
  • sales
    :销售业绩
  • rank
    :在公司内部按销售业绩降序排列的排名
  • next_behind
    :与下一名的销售业绩差距(如果是第一名则为0)
  • total_behind
    :与本部门最高销售业绩的差距
  • diff_from_avg
    :与销售平均值的差异

查询结果

comp_name dept_name sales rank next_behind total_behind diff_from_avg
CompanyA Dept 1 100 1 20 50 15
CompanyA Dept 3 80 2 0 70 -15
CompanyA Dept 4 80 2 0 70 -15
CompanyA Dept 2 50 4 30 100 -45
CompanyB Dept 4 120 1 30 40 22.5
CompanyB Dept 2 80 2 10 60 -12.5
CompanyB Dept 3 90 3 0 50 2.5
CompanyB Dept 1 70 4 20 70 -27.5

注意:

  • rank
    字段中,如果两个部门的销售业绩相同,则它们会共享相同的排名,并且下一个排名的数值会跳过(如上表中的CompanyA的Dept 3和Dept 4)。
  • next_behind
    字段计算的是与下一名销售业绩的差距,如果没有下一名(即已经是最后一名),则显示为0。
  • total_behind
    字段计算的是与本部门最高销售业绩的差距。
  • diff_from_avg
    字段计算的是当前部门的销售业绩与该公司所有部门销售业绩平均值的差异。正值表示高于平均值,负值表示低于平均值。


考虑到SQL查询的效率和简洁性,小悦随后优化方案一,决定采用窗口函数(如
RANK()
,
LEAD()
,
MAX()
,
AVG()
)来实现。

优化后的方案利用窗口函数直接在SELECT查询中完成所有计算,无需创建临时表来存储中间结果。
RANK()
函数用于计算销售业绩的排名,
LEAD()
函数(或
LAG()
,根据需求选择)用于找出与下一名销售业绩的差距,
MAX()

AVG()
窗口函数则分别用于计算本部门最高销售业绩和销售平均值,进而得出与这些值的差异。优化后的方案二:

--使用一条语句实现方案一中的临时表逻辑

--mysql8、oracle10g和mssql2012以上版本
SELECT temp1.*,  
RANK()
OVER (PARTITION BY comp_name ORDER BY sales DESC) ASrank,COALESCE(LAG(sales) OVER (PARTITION BY comp_name ORDER BY sales DESC) - sales, 0) ASnext_behind,MAX(sales) OVER (PARTITION BY comp_name) - sales AStotal_behind,
sales
- AVG(sales) OVER (PARTITION BY comp_name) ASdiff_from_avgFROMtemp1ORDER BYcomp_name, rank;--PostgreSQL版本 SELECT *,
RANK()
OVER w AS rank_by_sales, MAX(sales)OVER w - sales AS total_behind, sales - AVG(sales)OVER w AS diff_from_avg, COALESCE(LAG(sales)OVER w - sales, 0) ASnext_behindFROM temp1 WINDOW w AS (PARTITION BYcomp_nameORDER BY sales DESC);

方案二与方案一的查询结果是一样的。

在方案二的SQL查询中,使用了几个窗口函数(也称为分析函数)来对
temp1
表中的数据进行分组和排序,并计算每个部门相对于其公司内部其他部门的销售业绩指标。下面是对这些窗口函数的详细解释:

  1. RANK() OVER (...) AS rank:
    • RANK()
      是一个窗口函数,用于为分区内的行分配一个唯一的排名。但是,如果两行或多行具有相同的排序值(在本例中是
      sales
      ),则这些行将获得相同的排名,并且下一个排名将跳过相应的数量(即如果有两行并列第一,则下一行的排名将是第三,而不是第二)。
    • OVER (PARTITION BY comp_name ORDER BY sales DESC)
      指定了窗口函数的分区和排序方式。这里,
      PARTITION BY comp_name
      表示根据
      comp_name
      字段将数据分成不同的组(即每个公司的部门被视为一个组),而
      ORDER BY sales DESC
      表示在每个组内,数据将按照
      sales
      字段的降序进行排序。
  2. COALESCE(LAG(sales) OVER (...) - sales, 0) AS next_behind:
    • LAG(sales) OVER (...)
      是一个窗口函数,用于访问当前行之前的行的
      sales
      值(在本例中是按照
      sales
      的降序排列的)。如果没有前一行(即当前行是分区内的第一行),则
      LAG()
      函数将返回NULL。
    • COALESCE(expression, 0)
      函数用于将其参数列表中的第一个非NULL表达式作为结果返回。如果
      LAG(sales) - sales
      的结果是NULL(即当前行是分区内的第一行),则
      COALESCE
      将返回0。
    • 因此,
      next_behind
      列计算的是当前部门的销售业绩与紧随其后的部门销售业绩之间的差距。如果当前部门是第一名,则差距为0。
  3. MAX(sales) OVER (...) - sales AS total_behind:
    • MAX(sales) OVER (...)
      是一个窗口函数,用于计算分区内
      sales
      字段的最大值。由于这里使用了相同的分区和排序方式(
      PARTITION BY comp_name
      ),因此它将为每个部门提供该公司内部最高的销售业绩。
    • 然后,从这个最大值中减去当前行的
      sales
      值,得到
      total_behind
      ,即当前部门的销售业绩与该公司内部最高销售业绩之间的差距。
  4. sales - AVG(sales) OVER (...) AS diff_from_avg:
    • AVG(sales) OVER (...)
      是另一个窗口函数,用于计算分区内
      sales
      字段的平均值。同样,由于使用了
      PARTITION BY comp_name
      ,这将为每个部门提供该公司内部销售业绩的平均值。
    • 然后,从当前行的
      sales
      值中减去这个平均值,得到
      diff_from_avg
      ,即当前部门的销售业绩与该公司内部平均销售业绩之间的差异。正值表示当前部门的销售业绩高于平均值,负值表示低于平均值。

最后,
ORDER BY comp_name, rank
用于对整个查询结果进行排序,首先按公司名称(
comp_name
)升序排序,然后在每个公司内按销售排名(
rank
)升序排序。但是,由于
rank
已经是基于
sales
降序排列的,所以实际上在每个公司内,
rank
的排序已经是降序的,但外部的
ORDER BY
确保了不同公司之间的结果也是有序的。不过,由于
rank
本身在SQL中可能包含并列的情况,所以实际的排序可能会根据SQL实现的不同而略有差异(例如,在某些数据库中,并列的排名可能会以它们在表中的原始顺序显示)。


这里介绍一下 各大数据库中中常见的窗口分析函数
RANK()
,
OVER()
,
LAG()

  1. RANK()
    函数:


    • 这个函数最早出现在 IBM DB2 数据库中,可以追溯到 1993 年。
    • 随后,Oracle 在 2000 年的 Oracle9i 版本中引入了
      RANK()
      函数。
    • Microsoft SQL Server 则在 2005 年的 SQL Server 2005 版本中添加了对
      RANK()
      函数的支持。
    • RANK()
      函数的引入,为数据分析师和开发人员提供了一种更加灵活和强大的排序方式,极大地提升了数据分析的效率。
    • RANK() 函数用于给数据集中的行分配一个排名值。
      它会根据指定的列(或表达式)对行进行排序,然后给每一行分配一个排名值。
      如果有多行具有相同的值,它们将获得相同的排名,后续行的排名值会相应递增。
      这个函数在需要对数据进行排序并获取排名信息时非常有用,比如找出销售额前 5 名的产品。

  2. OVER()
    子句:


    • OVER()
      子句最早出现在 OLAP (Online Analytical Processing) 领域,可以追溯到 20 世纪 90 年代。
    • Oracle 在 2000 年的 Oracle9i 版本中引入了
      OVER()
      子句,使得窗口函数的使用更加方便和灵活。
    • Microsoft SQL Server 也在 2005 年的 SQL Server 2005 版本中添加了对
      OVER()
      子句的支持。
    • OVER()
      子句的引入,大大简化了复杂的数据分析任务,提高了开发人员的工作效率。
    • OVER() 子句用于定义窗口函数的作用范围。
      它可以指定行组的范围,从而允许窗口函数在这个范围内进行计算。
      常见的用法包括:计算移动平均值、计算累计值、计算排名

  3. LAG()

    LEAD()
    函数:


    • 这两个函数最早出现在 IBM DB2 数据库中,可以追溯到 2001 年。
    • Oracle 在 2006 年的 Oracle 10g 版本中引入了
      LAG()

      LEAD()
      函数。
    • Microsoft SQL Server 则在 2008 年的 SQL Server 2008 版本中添加了对这两个函数的支持。
    • LAG()

      LEAD()
      函数的引入,使得在进行时间序列分析和数据比较时更加方便和高效。
    • LAG() 函数用于获取当前行前N行的值。
      LEAD() 函数用于获取当前行后N行的值。
      这两个函数在进行时间序列分析和数据比较时非常有用,例如:
      计算当前销售额与上月销售额的差异
      判断当前季度的业绩是否高于上一季度

除了 Rank、Lag 和 Over 等常见的窗口函数外,SQL 还提供了其他一些有用的窗口函数,包括:

  1. Row_Number(): 为每个分区中的行分配一个唯一的序号,从 1 开始计数。

  2. Partition_By: 可以与其他窗口函数一起使用,用于指定窗口划分的依据。

  3. First_Value() 和 Last_Value(): 分别获取窗口内第一个值和最后一个值。

  4. Ntile(): 将分区中的行平均分配到指定数量的组中。

  5. Ratio_To_Report(): 计算当前行值占分区总值的比例。

  6. Cume_Dist(): 计算当前行小于等于自身值的行数占分区总行数的比例。

  7. Percent_Rank(): 计算当前行在分区内的百分位rank值。

  8. Dense_Rank(): 与 Rank() 类似,但不会出现并列序号的情况。

这些常用的窗口函数都是数据库技术发展的产物,随着 OLAP 和数据分析的需求不断增加,数据库厂商不断改进和完善这些功能,使得数据分析工作变得更加简单和高效。这些窗口函数的引入,极大地推动了数据分析领域的发展。

1. 引言

很多同学了解r-nacos特性后最开始只将r-nacos用于开发测试环境。

经过一段时间的使用后,部分同学有打算生产环境也从nacos迁移到r-nacos。

那么如何平衡地从nacos迁移到r-nacos呢?

r-nacos
简介:

r-nacos是一个用rust实现的nacos服务。相较于java nacos来说,是一个提供相同功能,启动更快、占用系统资源更小(初始内存小于10M)、性能更高、运行更稳定的服务。

r-nacos设计上完全兼容最新版本nacos面向client sdk 的协议(包含1.x的http OpenApi,和2.x的grpc协议), 支持使用nacos服务的应用平迁到 r-nacos。

2. 迁移计划

2.1 待迁移内容分析

迁移的目标已明确,在设计迁移计划前,我们需要先分析待迁移的数据。

从nacos迁移到r-nacos主要涉及4类数据:

  1. nacos鉴权用户数据
  2. 命名空间数据
  3. 配置数据
  4. 服务实例数据

nacos用户数据、命名空间数据与配置数据是持久化数据,需要事先完成设置与迁移。
服务实例数据是动态数据,切换后应该会自动注册,不需要事先迁移。

2.2 迁移阶段与步骤

我们可以把迁移分为3个阶段:

  1. 迁移前准备阶段;
  2. 迁移阶段;
  3. 迁移后收尾阶段;

一、迁移前准备

  1. 部署r-nacos,用于迁移前做数据迁移与数据录入。(如果nacos与r-nacos在同一台机器,可以用临时端口号启动r-nacos,等迁移时再更新端口号重启)
  2. 在r-nacos控制台录入应用依赖的用户信息,完成用户数据初始化。
  3. 在r-nacos控制台录入命名空间数据;
  4. 从nacos控制台按命名空间全量导出数据(每个命名空间会导出一个文件);然后分别把这部分配置数据通过r-nacos控制台导入对应命名空间中,完成配置数据迁移。

二、迁移中

数据迁移完成后即可开始切流迁移。

把应用请求流量切到r-nacos中,这一步不同的场景需要用不同的处理方式:

  1. 应用直接请求nacos服务场景:r-nacos需要和nacos在同一个机器替换它;需要先关闭nacos,再把r-nacos的端口改成原nacos端口启动,完成切换。
  2. 应用请求nginx后反向代理到nacos场景:更新nginx配置,把nacos反向代理地址更新为r-nacos地址,然后重新增加配置完成切换。

切流迁移完成后,注意观察应用与r-nacos的表现是否符合预期。

注意:就算目标是要开启鉴权,这个阶段r-naocs也不要启接口鉴权 (
RNACOS_ENABLE_OPEN_API_AUTH=false
),以防应用使用原nacos分配旧token请求被拦截。

三、迁移后收尾

完成迁移稳定运行一小段时间后,可以将nacos移除,只保留r-nacos。

如果需要对接口开启鉴权,则走以下操作:

a) 等应用旧token都过期(默认过期时间是5小时)都重新从r-nacos获取新token之后,再开启接口鉴权配置重启r-nacos(r-nacos可以秒级重启,应用几乎无感)。
b) 也可以分批重启应用强行其使用r-nacos token,之后再开启接口鉴权配置重启r-nacos。

至此完成从nacos迁移到r-nacos

3. 迁移案例

前面讲的是迁移操作步骤可能比较抽象,这里再补充两个具体迁移场景案例让读者更有体感。

3.1 应用服务直链单节点nacos的场景迁移方案

部署图:

nacos持久化内容:

nacos使用情况:

  1. 在10.0.24.9部署一台nacos,使用默认端口号8848,8948提供服务;
  2. nacos上设置两个命名空间pre,prod分别对预发、生产环境提供服务
  3. 有3个应用,每个应用2个实例使用nacos服务; 总共两套环境,其中一套环境共有3个配置文件,3个服务,6个实例;
  4. 应用使用的用户名:
    xxx_app_id
    ,密码:
    a07a6deb5e56

目标:
在同一台机器中部署r-nacos替换nacos提供服务,使用
systemd方式部署

3.1.1 迁移步骤——迁移前

步骤1
、 在10.0.24.9使用临时端口8858部署r-nacos。
这里假设使用linux systemd方式部署。具体部署方式参考r-nacos说明文档,这里不展开。
关键配置项如下:

# r-nacos监听http端口,这里使用临时端口8858,后面切流时调整回来
RNACOS_HTTP_PORT=8858
# r-nacos监听grpc端口,grpc端口这里不配置,默认值:HTTP端口+1000
# RNACOS_GRPC_PORT=9858
# r-nacos独立控制台端口,这个没有端口冲突,可以直接使用正式端口
RNACOS_HTTP_CONSOLE_PORT=10848
RNACOS_ENABLE_OPEN_API_AUTH=false

注:如果是docker启动方式,配置端口不用改,只需改对应对外映射的端口号即可

步骤2
、初始化用户信息
访问
http://10.0.24.9:10848/rnacos/
进入控制台。
增加应用依赖用户,用户名:
xxx_app_id
,密码: `a07a6deb5e56

步骤3
、初始化命名空间

初始化命名空间 pre、prod

步骤4
、迁移配置信息

  1. 从nacos导出配置

分别导出pre、prod命名空间下的配置

导出的文件名格式为:
nacos_config_export_20240721230938.zip

注意:每次导出后记得改下文件名增加命名空间标记,以防导入时选错文件。

  1. 把配置导入r-nacos
    分别把上一步nacos导出的配置文件包导出到r-nacos pre、prod命名空间下的配置。

a) 进入r-nacos控制台->配置列表页,选择对应命名空间

b) 上传导入配置文件

导入后:

上面图中操作的是pre命名空间,prod命名空间也操作一遍,这里不展开。

3.1.2 迁移步骤——迁移中

步骤5
、 完成数据初始化后关闭r-nacos,先把临时端口更新正式端口,方便后面可以直接启动r-nacos。

关闭r-nacos服务

systemctl stop rnacos

关键配置项如下:

properties
# r-nacos监听http端口
RNACOS_HTTP_PORT=8848
# r-nacos监听grpc端口,grpc端口这里不配置,默认值:HTTP端口+1000
# RNACOS_GRPC_PORT=9848
# r-nacos独立控制台端口
RNACOS_HTTP_CONSOLE_PORT=10848
# 切换过程中不开启接口鉴权
RNACOS_ENABLE_OPEN_API_AUTH=false

步骤6
、关闭nacos

# 切换到nacos目录,执行以下命名关闭nacos
./bin/shutdown.sh

步骤7
、启动r-nacos

配置信息在关闭nacos前已准备好,直接启动r-nacos服务:

systemctl start rnacos

3.1.3 迁移步骤——迁移后

步骤8
、切流完成,观察应用与r-nacos确认其是否正常工作。

应用服务可通过上游应用页面访问看看是否正常。
r-nacos可能通过监控页面查询其是否被应用访问。

步骤9
、开启r-nacos接口鉴权 (可选)

切流完成后过5个小时之后,开启r-nacos接口鉴权。

关键配置项如下:

properties
# r-nacos监听http端口
RNACOS_HTTP_PORT=8848
# r-nacos监听grpc端口,grpc端口这里不配置,默认值:HTTP端口+1000
# RNACOS_GRPC_PORT=9848
# r-nacos独立控制台端口
RNACOS_HTTP_CONSOLE_PORT=10848
# 开启接口鉴权
RNACOS_ENABLE_OPEN_API_AUTH=true

重启r-nacos服务:

systemctl start rnacos

至此,完成从nacos到r-nacos的迁移

3.2 应用服务通过nginx链接nacos场景迁移方案

部署图:

nacos内容和前一个场景一样,部署时中间多用了nginx代理提供服务。

通过nginx链接nacos,那么切换时只需要把nginx配置中原nacos地址切换为r-naocs地址,然后通过
nginx -s reload
重新加载配置规则即可完成切流。

3.2.1 迁移步骤——迁移前

步骤和前一个场景第1、2、3、4步一致,这里不展开。

3.2.2 迁移步骤——迁移中

步骤5
、更新nginx配置,把原nacos地址切换为r-naocs地址。

原nginx配置

# nacos http
server {
    listen       8848;
    listen  [::]:8848;
    server_name  localhost;

    location /nacos {
        proxy_set_header Host $proxy_host;
        proxy_pass http://10.0.24.9:8848;
    }
}

# nacos grpc
server {
    listen       9848;
    listen  [::]:9848;
    server_name  localhost;

    location /nacos {
        grpc_pass grpc://10.0.24.9:9848;
    }
}

更新后的nginx配置

# nacos http
server {
    listen       8848;
    listen  [::]:8848;
    server_name  localhost;

    location /nacos {
        proxy_set_header Host $proxy_host;
        proxy_pass http://10.0.24.9:8858;
    }
}

# nacos grpc
server {
    listen       9848;
    listen  [::]:9848;
    server_name  localhost;

    location /nacos {
        grpc_pass grpc://10.0.24.9:9858;
    }
}

执行
nginx -s reload
重新加载配置规则即可完成切流。

切换后:

3.2.3 迁移步骤——迁移后

步骤和前一个场景第8、9步一致,这里不展开。

总结

对于nacos迁移到r-nacos,分析待迁移数据项与划分好迁移各个阶段需要做的事项,可以设计出合适的迁移方案。

迁移可分在迁移前、迁移中、迁移后三个阶段,本文结合样例场景给出了较具体操作步骤。

如果上面的案例和自己的实际情况一致,可以考虑应用请求nacos前加一层nginx反向代理,之后再按案例2迁移即可。