wenmo8 发布的文章

前言

微服务架构已经成为搭建高效、可扩展系统的关键技术之一,然而,现有许多微服务框架往往过于复杂,使得我们普通开发者难以快速上手并体验到微服务带了的便利。为了解决这一问题,于是作者精心打造了一款最接地气的 .NET 微服务框架,帮助我们轻松构建和管理微服务应用。

本框架不仅支持 Consul 服务注册与发现,还自带了一系列高级特性,包括配置中心、链路跟踪(APM)、服务网关等,极大地简化了微服务的开发和运维过程。

此外框架还实现了 Saga 分布式事务、RabbitMQ 事件总线等功能,确保系统能够高效处理复杂的业务逻辑。更重要的是提供了一个人性化的 Dashboard 管理面板,使得监控和管理微服务集群变得方便。

通过本文的介绍大家可以学习到如何快速上手并充分利用这些特性,从而构建出既高效又稳定的微服务应用。

项目介绍

Wing 致力于打造一个功能强大且易于使用的 .NET 微服务框架,支持 .NET 6+ 运行平台。

该框架具备以下特点:

1、服务注册与发现
:支持 Consul 服务注册与发现机制,确保服务间的自动发现和动态管理。

2、服务间通讯
:支持 HTTP 和 gRPC 两种调用方式,内置负载均衡器,实现高效的服务间通信。

3、服务策略与异常处理
:提供服务策略配置,支持服务异常降级处理,确保系统的稳定性和可靠性。

4、Saga 分布式事务
:支持三种恢复策略(向前恢复、向后恢复、先前再后),确保事务的一致性和完整性。

5、配置中心
:内置配置中心,实现服务配置的在线集中统一管理。

6、链路追踪与性能监控
:支持 HTTP/gRPC/SQL 的链路追踪(APM)及耗时分析统计,帮助开发者快速定位性能瓶颈。

7、服务网关
:内置服务网关,支持全局服务策略和个性化服务策略配置,简化服务入口管理。

8、事件总线
:支持 RabbitMQ 事件总线,实现服务间的异步通信和事件传递。

9、管理界面
:提供人性化的 Dashboard 管理界面,便于监控和管理整个微服务集群。

Wing 框架为开发者提供一个强大而直观的开发平台,帮助快速构建和管理高效、可扩展的微服务应用。

快速入门

1、服务注册

什么是服务注册?

服务注册是指服务启动后将该服务的IP、端口等信息注册到
Consul

创建一个Web API 项目

提前准备:安装并启动Consul

打开 Visual Studio 2022 并创建Web API项目

安装依赖包

dotnet add package Wing.Consul

Program代码

builder.Services.AddWing();

添加配置

{//是否启用配置中心,默认启用
  "ConfigCenterEnabled": false,"Consul": {"Url": "http://localhost:8500","Service": {//Http  Grpc
      "Option": "Http","HealthCheck": {"Url": "http://localhost:1210/health",//单位:秒
        "Timeout": 10,//单位:秒
        "Interval": 10},"Name": "Wing.Demo_1.2.1","Host": "localhost","Port": 1210,"Tag": "","LoadBalancer": {//RoundRobin  WeightRoundRobin LeastConnection
        "Option": "WeightRoundRobin",//权重
        "Weight": 60},"Scheme": "http","Developer": "linguicheng"},//定时同步数据时间间隔,单位:秒 小于等于0表示立即响应
    "Interval": 10,//数据中心
    "DataCenter": "dc1",//等待时间,单位:分钟
    "WaitTime": 3}
}

查看运行效果

程序运行后,打开consul UI管理界面,可以看到注册服务
Wing.Demo_1.2
,具体如下图所示:

2、启动UI

Wing.UI

Wing
微服务框架中的一个可视化操作管理系统,主要功能有服务治理、配置中心、APM管理、Saga分布式事务查询。

安装依赖包

安装服务注册nuget包
Wing.Consul
,UI可视化界面管理nuget包
Wing.UI
,选择对应的数据库驱动(
参考FreeSql官网
),以SqlServer为例,安装
FreeSql.Provider.SqlServer

dotnet add package Wing.UI
dotnet add package FreeSql.Provider.SqlServer

Program代码

using Wing;var builder =WebApplication.CreateBuilder(args);
builder.Host.AddWing(builder
=>builder.AddConsul());
builder.Services.AddWing().AddWingUI(FreeSql.DataType.SqlServer);

查看运行效果

程序运行后,浏览器访问 ,运行效果如下图:

可以看到示例
1.2
注入的服务`Wing.Demo_1.2

3、服务发现与调用

什么是服务发现?

服务发现是指服务启动后将服务注册信息定时同步刷新到本地或实时获取
Consul
的服务信息。

安装依赖包

dotnet add package Wing.Consul

Grpc健康检查

protobuf文件

syntax = "proto3";
package grpc.health.v1;
message HealthCheckRequest {
string service
= 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN
= 0;
SERVING
= 1;
NOT_SERVING
= 2;
}
ServingStatus status
= 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

HealthCheck代码

public classHealthCheck : Health.HealthBase
{
public override Task<HealthCheckResponse>Check(HealthCheckRequest request, ServerCallContext context)
{
return Task.FromResult(new HealthCheckResponse() { Status =HealthCheckResponse.Types.ServingStatus.Serving });
}
public override async Task Watch(HealthCheckRequest request, IServerStreamWriter<HealthCheckResponse>responseStream, ServerCallContext context)
{
await responseStream.WriteAsync(newHealthCheckResponse()
{ Status
=HealthCheckResponse.Types.ServingStatus.Serving });
}
}

Program代码

usingWing;var builder =WebApplication.CreateBuilder(args);
builder.Host.AddWing(builder
=>builder.AddConsul());//Add services to the container. builder.Services.AddGrpc();
builder.Services.AddWing();
var app =builder.Build();//Configure the HTTP request pipeline. app.MapGrpcService<GreeterService>();
app.MapGet(
"/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();

运行效果

运行当前程序并启动示例
1.3
,浏览器访问 ,可以看到注入的Grpc服务
Wing.Demo_1.4
,运行效果如下图:

在示例
1.2
中调用当前Grpc服务中
SayHello
方法,代码如下:

[HttpGet("hello")]public Task<string>SayHello()
{
return _serviceFactory.InvokeAsync("Wing.Demo_1.4", async serviceAddr =>{var channel =GrpcChannel.ForAddress(serviceAddr.ToString());var greeterClient = newGreeter.GreeterClient(channel);var result = await greeterClient.SayHelloAsync(new HelloRequest { Name = "Wing"});returnresult.Message;
});
}

运行示例
1.2
,浏览器访问
http://localhost:1210/weatherforecast/hello
,运行效果如下图:

4、启动服务网关

服务网关是系统对外的唯一入口,它封装了系统内部架构,为每个客户端提供了定制的API,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有非业务功能。

安装依赖包

安装服务注册nuget包
Wing.Consul
,服务网关nuget包
Wing.Gateway
,选择对应的数据库驱动(
参考FreeSql官网open in new window
),以SqlServer为例,安装
FreeSql.Provider.SqlServer
,请求日志支持本地消息队列和分布式消息队列进行异步持久化,基本上不影响网关性能。如果不想记录请求日志,可以不安装该包。

如果想启用
EventBus
记录请求日志,需要安装RabbitMQ nuget包
Wing.RabbitMQ

dotnet add package Wing.Consul
dotnet add package Wing.Gateway
dotnet add package Wing.RabbitMQ
dotnet add package FreeSql.Provider.SqlServer

Program代码

usingWing;
var builder =WebApplication.CreateBuilder(args);

builder.Host.AddWing(builder
=>builder.AddConsul());
//Add services to the container.
builder.Services.AddControllers();

builder.Services.AddWing()
.AddPersistence(FreeSql.DataType.SqlServer)
.AddGateWay()
.AddEventBus();
//如果不想使用EventBus记录请求日志,可以删除此行代码 var app =builder.Build();
//Configure the HTTP request pipeline.
app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

添加配置

{//是否启用配置中心,默认启用
  "ConfigCenterEnabled": false,"Consul": {"Url": "http://localhost:8500","Service": {//Http  Grpc
      "Option": "Http","HealthCheck": {"Url": "http://localhost:1510/health",//单位:秒
        "Timeout": 10,//单位:秒
        "Interval": 10},"Name": "Wing.Demo_1.5","Host": "localhost","Port": 1510,"LoadBalancer": {//RoundRobin  WeightRoundRobin LeastConnection
        "Option": "WeightRoundRobin",//权重
        "Weight": 50},"Scheme": "http","Developer": "linguicheng"},//定时同步数据时间间隔,单位:秒 小于等于0表示立即响应
    "Interval": 10,//数据中心
    "DataCenter": "dc1",//等待时间,单位:分钟
    "WaitTime": 3},"ConnectionStrings": {"Wing": "Data Source=192.168.56.96;User Id=sa;Password=wing123.;Initial Catalog=Wing;TrustServerCertificate=true;Pooling=true;Min Pool Size=1"},//自动同步实体结构到数据库
  "UseAutoSyncStructure": true,//如果不启用EventBus,可以删除RabbitMQ配置
  "RabbitMQ": {"HostName": "192.168.56.99","UserName": "admin","Password": "admin","VirtualHost": "/","Port": 5672,//消息过期时间,单位:毫秒,过期会自动路由到死信队列,小于或等于0则永久有效
    "MessageTTL": 0,"ExchangeName": "Sample.GateWay",//每次投递消息数量
    "PrefetchCount": 1},"Gateway": {//请求日志
    "Log": {//是否启用网关日志记录
      "IsEnabled": true,//是否启用事件总线(RabbitMQ)存储日志,生产环境推荐启用,可以提升程序的性能
      "UseEventBus": false}
}
}

查看运行效果

运行示例
1.2
并启动当前示例程序,浏览器访问,运行效果如下图:

运行示例
1.3
,浏览器访问,可以看到网关请求日志,运行效果如下图:

服务地址组成

请求服务地址默认是{网关IP或域名}/{服务名}/{服务路由},例如:
http://localhost:1510/Wing.Demo_1.2/weatherforecast

注意:
更多具体内容可以访问Wing 官方文档,具体内容如下所示

项目地址

Github:
https://linguicheng.github.io/Wing

Gitee:
https://gitee.com/linguicheng/Wing

文档地址:
https://linguicheng.github.io/Wing

示例地址:
https://gitee.com/linguicheng/wing-demo

开源协议:
基于MIT协议永久开源免费使用

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号
[DotNet技术匠]
社区,与其他热爱技术的同行一起交流心得,共同成长!

写在前面:本文所述未必符合当前最新情形(包括蓝牙技术发展、微信小程序接口迭代等)。

微信小程序为蓝牙操作提供了很多接口,但在实际开发过程中,会发现隐藏了不少坑。目前主流蓝牙应用都是基于
低功耗蓝牙(BLE)
的,本文介绍相关的几个基础接口,并对其进行封装,便于业务层调用。

蓝牙发展

在开发蓝牙应用程序之前,有必要对蓝牙这项技术做大致了解。

经典蓝牙

一种短距离无线通信标准,运行在 2.4GHz 频段,主要用于两个设备之间的数据传输。

一般将蓝牙 4.0 之前的版本称为
经典蓝牙
,其传输速率在 1-3Mbps 之间。虽然有着不错的传输速率,但由于功耗较大,难以满足移动终端和物联网的需求,逐渐被更先进的版本所取代。‌

低功耗蓝牙(BLE)

蓝牙 4.0‌ 引入了
低功耗蓝牙(BLE)
技术,其最大数据吞吐量仅为1Mbps,但相对经典蓝牙,BLE 拥有超低的运行功耗和待机功耗。

BLE 的低功耗是如何做到的呢?主要是缩减广播通道数量(由经典蓝牙的 16-32个,缩减为 3 个)、缩短广播射频开启时间(由经典蓝牙的 22.5ms,减少到 0.6-1.2ms)、深度睡眠模式及针对低功耗场景优化了协议栈等,此处不赘述。

当前最新版本

‌当前大版本是蓝牙 5.0,传输速度达到了 24Mbps,是 4.2 版本的两倍,有效工作距离可达 300 米,是 4.2 版本的四倍。低功耗模式下的传输速度上限为 2Mbps,适合于影音级应用,如高清晰度音频解码协议的应用。

蓝牙特征值

GATT(Generic Attribute Profile)协议
定义了蓝牙设备之间的通信方式,其中单个
服务(Service)
可以包含多个
特征值(Characteristic)
,每个服务和特征值都有特定的‌ UUID 来唯一标识。特征值是蓝牙设备中用于存储和传输数据的基本单元,每个特征值都有其特定的
属性和值

属性协议(ATT)
定义数据的检索,允许设备暴露数据给其他设备,这些数据被称为
属性(attribute)

通过属性可以设置特征值操作类型,如
读取、写入、通知
等,操作对象即为特征值的
值(value)
。一个特征值可以同时拥有多种操作类型。

为了实现数据的传输,服务需要暴露两个主要的特征值:
write
和‌
notify 或 indication
。write 特征值用于接收数据,而 notify 特征值用于发送数据。这些特征值类型为 bytes,并且一次传输的数据长度可以根据不同的特征值类型有所不同。

小程序接口封装

需要知道的是,虽然蓝牙是开放协议,但由于苹果 IOS 系统的封闭设计,目前苹果设备无法与 Android 及其它平台设备通过蓝牙相连。

本文描述皆基于 Android 平台。

关键接口

使用蓝牙传输数据都会涉及以下步骤及接口:

  1. 激活设备蓝牙(如在手机上点按蓝牙图标);
  2. wx.openBluetoothAdapter
    :初始化小程序蓝牙模块;
  3. 搜索外围设备
    1. wx.onBluetoothDeviceFound
      :监听搜索到新设备的事件;
    2. wx.startBluetoothDevicesDiscovery
      :开始搜索附近设备;
    3. wx.stopBluetoothDevicesDiscovery
      :找到待连的对手设备后停止搜索;
  4. wx.createBLEConnection
    :连接 BLE 设备;
  5. 接收数据
    1. wx.notifyBLECharacteristicValueChange
      :为下一步骤做铺垫(注意:必须对手设备的特征支持 notify 或者 indicate 才可以成功调用);
    2. wx.onBLECharacteristicValueChange
      :监听对手设备特征值变化事件,可以获得变化后的特征 value,如此数据就从对手设备传递过来了;
  6. wx.writeBLECharacteristicValue
    :向对手设备特征值中写入二进制数据(注意:必须对手设备的特征支持 write 才可以成功调用);
  7. wx.closeBLEConnection
    :断开连接;
  8. wx.closeBluetoothAdapter
    :关闭小程序蓝牙模块;
  9. 关闭设备蓝牙。

坑及注意点(仅限于笔者基于开发过程使用到的机型观察记录,未必有普遍性):

  • wx.onBluetoothDeviceFound 这个方法只能找到新的蓝牙设备,之前搜索过的在部分安卓机型上,不算做新的蓝牙设备,因此重新搜索不到。这种情况,要么重启小程序蓝牙模块或者重启小程序,或者使用
    wx.getBluetoothDevices
    获取在蓝牙模块生效期间所有搜索到的蓝牙设备。
  • 连接未必能一次成功,需要多连几次。
  • 每次连接最好能重启 BluetoothAdapter,否则在后续 wx.notifyBLECharacteristicValueChange 时容易报 10005-没有找到指定特征 错误。
  • 若小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需再次进行搜索操作。
  • 系统与蓝牙设备会限制蓝牙 4.0 单次传输的数据大小,超过最大字节数后会发生写入错误,建议每次写入不超过 20 字节。
  • 一旦过程中出现任何异常,就必须断开连接重连,否则后续会一直报 notifyblecharacteristicValuechange:fail: no characteristic 错误

主要代码

注:本文代码块为笔者临时盲敲,仅作参考。

定义一个工具对象

const ble = {}

由于可能会遇到的各类问题,我们先全局定义运行时异常枚举和 throw/handle 方法,免得后面遇到异常处理各写各的。

const ble = {
  errors: {
    OPEN_ADAPTER: '开启蓝牙模块异常',
    CLOSE_ADAPTER: '关闭蓝牙模块异常',
    CONNECT: '蓝牙连接异常',
    NOTIFY_CHARACTERISTIC_VALUE_CHANGE: '注册特征值变化异常',
    WRITE: '发送数据异常',
    DISCONNECT: '断开蓝牙连接异常',
    //...
  },

  _throwError(title, err) {
    //... 可以考虑在这里调用 wx.closeBLEConnection

    if (err) {
      err.title = title
      throw err
    }
    throw new Error(title)
  },  

蓝牙连接。注意到这是个有限递归方法,且每次连接都先重启 BluetoothAdapter,原因请看上节。

/** 
   * @param {string} deviceId 设备号
   * @param {int} tryCount 已尝试次数
   */
  async connectBLE(deviceId, tryCount = 5) {
    await wx.closeBluetoothAdapter().catch(err => { ble._throwError(this.errors.CLOSE_ADAPTER, err) })
    await wx.openBluetoothAdapter().catch(err => { ble._throwError(this.errors.OPEN_ADAPTER, err) })
    await wx.createBLEConnection({
      deviceId: deviceId,
      timeout: 5000
    })
      .catch(async err => {
        if (err.errCode === -1) { //蓝牙已是连接状态
          // continue work
        } else {
          console.log(`第${6 - tryCount}次蓝牙连接出错`, err.errCode, err.errMsg)
          tryCount--
          if (tryCount === 0) {
            ble._throwError(this.errors.CONNECT, err)
          } else {
            await ble.connectBLE(deviceId, tryCount)
          }
        }
      })
      //蓝牙连接成功
  },

连接成功后,可能需要监听对手设备,用于接收其传过来的数据。

  async onDataReceive(deviceId, serviceId, characteristicId, callback) {
    await wx.notifyBLECharacteristicValueChange({
      deviceId: deviceId,
      serviceId: serviceId,
      characteristicId: characteristicId,
      state: true
    }).catch(err => { ble._throwError(this.errors.NOTIFY_CHARACTERISTIC_VALUE_CHANGE, err) })

    wx.onBLECharacteristicValueChange(res => {
      let data = new Uint8Array(res.value)
      callback(data)
    })
  },

发送数据,须切片,每次发送不多于 20字节。此处增加了在固定时长内的重试机制。

  /** 
   * @param {Uint8ClampedArray} data 待发送数据
   * @param {boolean} holdConnWhenDone 发送完毕后是否保持连接
   */
  async send(deviceId, serviceId, characteristicId, data, holdConnWhenDone = false) {
    let idx = 0 //已传输字节数
    let startTime = Date.now(),
      duration = 800 //发送失败重试持续时间  
    while (idx < data.byteLength) {
      await wx.writeBLECharacteristicValue({
        deviceId: deviceId,
        serviceId: serviceId,
        characteristicId: characteristicId,
        value: data.slice(idx, idx += 20).buffer
      })
        .then(_ => startTime = Date.now()) //成功则now重置
        .catch(err => {
          if (Date.now() - startTime >= duration) {
            ble._throwError(this.errors.WRITE, err)
          } else {
            //重试
            idx -= 20
          }
        })
    }
    if (!holdConnWhenDone)
      await wx.closeBLEConnection({ deviceId: deviceId }).catch(err => { ble._throwError(this.errors.DISCONNECT, err) })
  }

在实际项目中,可能需要在每次发送数据片之后得到对手设备响应后,根据响应决定重发(校验错误或响应超时等)、中止(设备繁忙)、还是接着发送下一个数据片。这种情况则需配合 onDataReceive 方法协同工作,向其传入合适的 callback 参数,此处不赘述。

技术背景

假设我们在一个局域网内有多台工作站(不是服务器),那么有没有一个简单的方案可以实现一个小集群,提交分布式的任务呢?
Ray
为我们提供了一个很好的解决方案,允许你通过conda和Python灵活的构建集群环境,并提交分布式的任务。其基本架构为:

那么本文简单的介绍一下Ray的安装与基本使用。

安装

由于是一个Python的框架,Ray可以直接使用pip进行安装和管理:

$ python3 -m pip install ray[default]

但是需要注意的是,在所有需要构建集群的设备上,需要统一Python和Ray的版本,因此建议先使用conda创建同样的虚拟环境之后,再安装统一版本的ray。否则在添加集群节点的时候就有可能出现如下问题:

RuntimeError: Version mismatch: The cluster was started with:
    Ray: 2.7.2
    Python: 3.7.13
This process on node 172.17.0.2 was started with:
    Ray: 2.7.2
    Python: 3.7.5

启动和连接服务

一般在配置集群的时候可以先配置下密钥登陆:

$ ssh-keygen -t rsa
$ ssh-copy-id user_name@ip_address

就这么两步,就可以配置远程服务器ssh免密登陆(配置的过程中有可能需要输入一次密码)。然后在主节点(配置一个master节点)启动ray服务:

$ ray start --head --dashboard-host='0.0.0.0' --dashboard-port=8265
Usage stats collection is enabled. To disable this, add `--disable-usage-stats` to the command that starts the cluster, or run the following command: `ray disable-usage-stats` before starting the cluster. See https://docs.ray.io/en/master/cluster/usage-stats.html for more details.

Local node IP: xxx.xxx.xxx.xxx

--------------------
Ray runtime started.
--------------------

Next steps
  To add another node to this Ray cluster, run
    ray start --address='xxx.xxx.xxx.xxx:6379'

  To connect to this Ray cluster:
    import ray
    ray.init()

  To submit a Ray job using the Ray Jobs CLI:
    RAY_ADDRESS='http://xxx.xxx.xxx.xxx:8265' ray job submit --working-dir . -- python my_script.py

  See https://docs.ray.io/en/latest/cluster/running-applications/job-submission/index.html
  for more information on submitting Ray jobs to the Ray cluster.

  To terminate the Ray runtime, run
    ray stop

  To view the status of the cluster, use
    ray status

  To monitor and debug Ray, view the dashboard at
    xxx.xxx.xxx.xxx:8265

  If connection to the dashboard fails, check your firewall settings and network configuration.

这就启动完成了,并给你指示了下一步的操作,例如在另一个节点上配置添加到集群中,可以使用指令:

$ ray start --address='xxx.xxx.xxx.xxx:6379'

但是前面提到了,这里要求Python和Ray版本要一致,如果版本不一致就会出现这样的报错:

RuntimeError: Version mismatch: The cluster was started with:
    Ray: 2.7.2
    Python: 3.7.13
This process on node 172.17.0.2 was started with:
    Ray: 2.7.2
    Python: 3.7.5

到这里其实Ray集群就已经部署完成了,非常的简单方便。

基础使用

我们先用一个最简单的案例来测试一下:

# test_ray.py 
import os
import ray

ray.init()

print('''This cluster consists of
    {} nodes in total
    {} CPU resources in total
'''.format(len(ray.nodes()), ray.cluster_resources()['CPU']))

这个Python脚本打印了远程节点的计算资源,那么我们可以用这样的方式去提交一个本地的job:

$ RAY_ADDRESS='http://xxx.xxx.xxx.xxx:8265' ray job submit --working-dir . -- python test_ray.py 
Job submission server address: http://xxx.xxx.xxx.xxx:8265
2024-08-27 07:35:10,751 INFO dashboard_sdk.py:338 -- Uploading package gcs://_ray_pkg_4b79155b5de665ce.zip.
2024-08-27 07:35:10,751 INFO packaging.py:518 -- Creating a file package for local directory '.'.

-------------------------------------------------------
Job 'raysubmit_7Uqy8LjP4dxjZxGa' submitted successfully
-------------------------------------------------------

Next steps
  Query the logs of the job:
    ray job logs raysubmit_7Uqy8LjP4dxjZxGa
  Query the status of the job:
    ray job status raysubmit_7Uqy8LjP4dxjZxGa
  Request the job to be stopped:
    ray job stop raysubmit_7Uqy8LjP4dxjZxGa

Tailing logs until the job exits (disable with --no-wait):
2024-08-27 15:35:14,079 INFO worker.py:1330 -- Using address xxx.xxx.xxx.xxx:6379 set in the environment variable RAY_ADDRESS
2024-08-27 15:35:14,079 INFO worker.py:1458 -- Connecting to existing Ray cluster at address: xxx.xxx.xxx.xxx:6379...
2024-08-27 15:35:14,103 INFO worker.py:1639 -- Connected to Ray cluster. View the dashboard at http://xxx.xxx.xxx.xxx:8265 
This cluster consists of
    1 nodes in total
    48.0 CPU resources in total


------------------------------------------
Job 'raysubmit_7Uqy8LjP4dxjZxGa' succeeded
------------------------------------------

这里的信息说明,远程的集群只有一个节点,该节点上有48个可用的CPU核资源。这些输出信息不仅可以在终端窗口上看到,还可以从这里给出的dashboard链接里面看到更加详细的任务管理情况:

这里也顺便提交一个输出软件位置信息的指令,确认下任务是在远程执行而不是在本地执行:

import ray

ray.init()

import numpy as np
print (np.__file__)

返回的日志为:

$ RAY_ADDRESS='http://xxx.xxx.xxx.xxx:8265' ray job submit --working-dir . -- python test_ray.py 
Job submission server address: http://xxx.xxx.xxx.xxx:8265
2024-08-27 07:46:10,645 INFO dashboard_sdk.py:338 -- Uploading package gcs://_ray_pkg_5bba1a7144beb522.zip.
2024-08-27 07:46:10,658 INFO packaging.py:518 -- Creating a file package for local directory '.'.

-------------------------------------------------------
Job 'raysubmit_kQ3XgE4Hxp3dkmuU' submitted successfully
-------------------------------------------------------

Next steps
  Query the logs of the job:
    ray job logs raysubmit_kQ3XgE4Hxp3dkmuU
  Query the status of the job:
    ray job status raysubmit_kQ3XgE4Hxp3dkmuU
  Request the job to be stopped:
    ray job stop raysubmit_kQ3XgE4Hxp3dkmuU

Tailing logs until the job exits (disable with --no-wait):
2024-08-27 15:46:12,456 INFO worker.py:1330 -- Using address xxx.xxx.xxx.xxx:6379 set in the environment variable RAY_ADDRESS
2024-08-27 15:46:12,457 INFO worker.py:1458 -- Connecting to existing Ray cluster at address: xxx.xxx.xxx.xxx:6379...
2024-08-27 15:46:12,470 INFO worker.py:1639 -- Connected to Ray cluster. View the dashboard at http://xxx.xxx.xxx.xxx:8265 
/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/numpy/__init__.py

------------------------------------------
Job 'raysubmit_kQ3XgE4Hxp3dkmuU' succeeded
------------------------------------------

$ python3 -m pip show numpy
Name: numpy
Version: 1.21.6
Summary: NumPy is the fundamental package for array computing with Python.
Home-page: https://www.numpy.org
Author: Travis E. Oliphant et al.
Author-email: 
License: BSD
Location: /usr/local/python-3.7.5/lib/python3.7/site-packages
Requires: 
Required-by: CyFES, h5py, hadder, matplotlib, mindinsight, mindspore, mindspore-serving, pandas, ray, scikit-learn, scipy

这里可以看到,提交的任务中numpy是保存在mindspore-latest虚拟环境中的,而本地的numpy不在虚拟环境中,说明任务确实是在远程执行的。类似的可以在dashboard上面看到提交日志:

接下来测试一下分布式框架ray的并发特性:

import ray

ray.init()

@ray.remote(num_returns=1)
def cpu_task():
    import time
    time.sleep(2)
    import numpy as np
    nums = 100000
    arr = np.random.random((2, nums))
    arr2 = arr[1]**2 + arr[0]**2
    pi = np.where(arr2<=1, 1, 0).sum() * 4 / nums
    return pi

num_conc = 10
res = ray.get([cpu_task.remote() for _ in range(num_conc)])
print (sum(res) / num_conc)

这个案例的内容是用蒙特卡洛算法计算圆周率的值,一次提交10个任务,每个任务中撒点100000个,并休眠2s。那么如果是顺序执行的话,理论上需要休眠20s。而这里提交任务之后,输出如下:

$ time RAY_ADDRESS='http://xxx.xxx.xxx.xxx:8265' ray job submit --working-dir . --entrypoint-num-cpus 10 -- python te
st_ray.py 
Job submission server address: http://xxx.xxx.xxx.xxx:8265
2024-08-27 08:30:13,315 INFO dashboard_sdk.py:385 -- Package gcs://_ray_pkg_d66b052eb6944465.zip already exists, skipping upload.

-------------------------------------------------------
Job 'raysubmit_Ur6MAvP7DYiCT6Uz' submitted successfully
-------------------------------------------------------

Next steps
  Query the logs of the job:
    ray job logs raysubmit_Ur6MAvP7DYiCT6Uz
  Query the status of the job:
    ray job status raysubmit_Ur6MAvP7DYiCT6Uz
  Request the job to be stopped:
    ray job stop raysubmit_Ur6MAvP7DYiCT6Uz

Tailing logs until the job exits (disable with --no-wait):
2024-08-27 16:30:15,032 INFO worker.py:1330 -- Using address xxx.xxx.xxx.xxx:6379 set in the environment variable RAY_ADDRESS
2024-08-27 16:30:15,033 INFO worker.py:1458 -- Connecting to existing Ray cluster at address: xxx.xxx.xxx.xxx:6379...
2024-08-27 16:30:15,058 INFO worker.py:1639 -- Connected to Ray cluster. View the dashboard at http://xxx.xxx.xxx.xxx:8265 
3.141656

------------------------------------------
Job 'raysubmit_Ur6MAvP7DYiCT6Uz' succeeded
------------------------------------------


real    0m7.656s
user    0m0.414s
sys     0m0.010s

总的运行时间在7.656秒,其中5s左右的时间是来自网络delay。所以实际上并发之后的总运行时间就在2s左右,跟单任务休眠的时间差不多。也就是说,远程提交的任务确实是并发执行的。最终返回的结果进行加和处理,得到的圆周率估计为:
3.141656
。而且除了普通的CPU任务之外,还可以上传GPU任务:

import ray

ray.init()

@ray.remote(num_returns=1, num_gpus=1)
def test_ms():
    import os
    os.environ['GLOG_v']='4'
    os.environ['CUDA_VISIBLE_DEVICE']='0'
    import mindspore as ms
    ms.set_context(device_target="GPU", device_id=0)
    a = ms.Tensor([1, 2, 3], ms.float32)
    return a.asnumpy().sum()

res = ray.get(test_ms.remote())
ray.shutdown()
print (res)

这个任务是用mindspore简单创建了一个Tensor,并计算了Tensor的总和返回给本地,输出内容为:

$ RAY_ADDRESS='http://xxx.xxx.xxx.xxx:8265' ray job submit --working-dir . --entrypoint-num-gpus 1 -- python test_ray.py 
Job submission server address: http://xxx.xxx.xxx.xxx:8265
2024-08-28 01:16:38,712 INFO dashboard_sdk.py:338 -- Uploading package gcs://_ray_pkg_10019cd9fa9bdc38.zip.
2024-08-28 01:16:38,712 INFO packaging.py:518 -- Creating a file package for local directory '.'.

-------------------------------------------------------
Job 'raysubmit_RUvkEqnkjNitKmnJ' submitted successfully
-------------------------------------------------------

Next steps
  Query the logs of the job:
    ray job logs raysubmit_RUvkEqnkjNitKmnJ
  Query the status of the job:
    ray job status raysubmit_RUvkEqnkjNitKmnJ
  Request the job to be stopped:
    ray job stop raysubmit_RUvkEqnkjNitKmnJ

Tailing logs until the job exits (disable with --no-wait):
2024-08-28 09:16:41,960 INFO worker.py:1330 -- Using address xxx.xxx.xxx.xxx:6379 set in the environment variable RAY_ADDRESS
2024-08-28 09:16:41,960 INFO worker.py:1458 -- Connecting to existing Ray cluster at address: xxx.xxx.xxx.xxx:6379...
2024-08-28 09:16:41,974 INFO worker.py:1639 -- Connected to Ray cluster. View the dashboard at http://xxx.xxx.xxx.xxx:8265 
6.0

------------------------------------------
Job 'raysubmit_RUvkEqnkjNitKmnJ' succeeded
------------------------------------------

返回的计算结果是6.0,那么也是正确的。

查看和管理任务

前面的任务输出信息中,都有相应的job_id,我们可以根据这个job_id在主节点上面查看相关任务的执行情况:

$ ray job status raysubmit_RUvkEqnkjNitKmnJ

可以查看该任务的输出内容:

$ ray job logs raysubmit_RUvkEqnkjNitKmnJ

还可以终止该任务的运行:

$ ray job stop raysubmit_RUvkEqnkjNitKmnJ

总结概要

本文介绍了基于Python的分布式框架Ray的基本安装与使用。Ray框架下不仅可以通过conda和Python十分方便的构建一个集群,还可以自动的对分布式任务进行并发处理,且支持GPU分布式任务的提交,极大的简化了手动分布式开发的工作量。

版权声明

本文首发链接为:
https://www.cnblogs.com/dechinphy/p/ray.html

作者ID:DechinPhy

更多原著文章:
https://www.cnblogs.com/dechinphy/

请博主喝咖啡:
https://www.cnblogs.com/dechinphy/gallery/image/379634.html

VMware vSAN 解决方案中,为了充分利用 vSAN HCI 集群内的存储资源, vSAN HCI 和 vSAN HCI 集群之间可以相互共享存储资源,这种解决方案早期叫 vSAN HCI Mesh,现在被称为具有数据存储共享的 vSAN HCI(
vSAN HCI with datastore sharing
)。VMware vSAN 集群根据主机磁盘的组成方式分为 Original Storage Architecture(OSA)原始存储架构和 Express Storage Architecture(ESA)快速存储架构,根据部署架构所使用的类型分为 vSAN 计算集群(Compute Only)、vSAN HCI 集群(Compute & Storage)以及 vSAN Max 集群(Storage Only)。

VMware Cloud Foundation 解决方案中可以添加不同 vSAN 类型的主机并创建用于不同用途的 vSAN 集群类型,并且在 SDDC Manager 中可以直接管理并挂载远程 vSAN 数据存储。VMware Cloud Foundation 5.2 版本开始增加对 vSAN Max 分解存储(Disaggregated Storage)类型的支持,你在增加 VI 工作负载域或者创建新集群的时候可以将 vSAN Max 集群的存储作为主体存储(Principal Storage)进行使用,但是,当前 VCF 还不支持 vSAN Max 延伸集群架构。有关更多关于 vSAN Max 分解存储的内容和细节请查看这篇(
创建vSAN Max集群并配置挂载远程数据存储。
)文章。

使用挂载远程 vSAN 数据存储功能具有许多要求和注意事项,挂载端集群被称为客户端(Client)集群,被挂载端集群被称为服务器端(Server)集群,客户端集群和服务器端集群之间挂载和被挂载具有一定的数量限制,客户端集群和服务器端集群之间共享存储,对于两者之间部署架构的类型也有限制,vSAN 计算集群可以挂载 vSAN HCI OSA/ESA 集群和 vSAN Max 集群,vSAN HCI OSA 集群只能挂载 vSAN HCI OSA 集群,vSAN HCI ESA 集群只能挂载 vSAN HCI ESA 集群和 vSAN Max 集群等。下面来看看具体的配置过程。

一、vSAN 计算集群

vSAN 计算集群其实就是标准的 vSphere 集群,本身不存在本地/共享存储,只提供计算资源。通过配置为 vSAN 计算集群,会在集群的主机之上安装一个精简的 vSAN,用于作为客户端集群,然后挂载远程服务器端集群(vSAN HCI 集群/vSAN Max 集群)上的存储资源后,提供给本集群中的工作负载进行使用。

导航到 SDDC Manager->清单->主机,点击“服役主机”添加用于 vSAN 计算集群的 ESXi 主机。选择“vSAN”存储类型并选择 vSAN 类型为“vSAN 计算集群”,其他选项可根据情况自行进行调整,然后完成服役主机添加工作流。

由于是 vSAN 集群,因此你还是需要至少准备 3 台主机以用于 vSAN 计算集群,使用上述同样的方式完成其他主机添加即可,主机服役后会显示在未分配的主机列表当中,如下图所示。

导航到 SDDC Manager->清单->工作负载域,在管理域左边三个点处点击“添加集群”。

存储类型选择“vSAN”,点击开始。

设置 vSAN 计算集群的名称(vcf-mgmt01-cluster02),点击下一步。

选择 vSAN 集群类型为“vSAN 计算集群”,点击下一步。

选择当前可挂载的远程 vSAN 数据存储,管理域默认集群是 vSAN HCI,所以可供 vSAN 计算集群挂载使用,点击下一步。

选择用于 vSAN 计算集群的 ESXi 主机,点击下一步。

配置 vSAN 计算集群主机的虚拟交换机,选择创建自定义交换机配置。

配置过程就忽略了,请注意主机配置用于 vSAN 网络流量的交换机所分配的 vSAN VMkernel 网卡需要与挂载远程的 vSAN 集群的 vSAN VMkernel 网卡互通,后面也是一样。

配置用于 vSAN 计算集群的许可证,这个地方需要选择 VMware vSAN 许可证,其实 vSAN 计算集群是不需要 vSAN 许可证的,但是这里要选择,点击下一步。

检查配置,点击完成。

下图可以看到,只用到 VMware vSphere 许可证。

如果一切顺利,导航到 SDDC Manager->清单->工作负载域->集群,可以看到 vSAN 计算集群已经创建成功,在 vSAN 配置处显示了配置挂载远程 vSAN 数据存储的集群状态。

点击进入该集群,能够看到 vSAN 计算集群挂载 vSAN HCI 集群存储的拓扑。

在服务器端集群中,也能看到挂载的客户端集群拓扑。

在 vCenter Server(vSphere Client)中查看 vSAN 计算集群的状态。

查看 vSAN 计算集群挂载的远程 vSAN 数据存储。

二、vSAN HCI 集群

vSAN HCI 集群就是标准的 vSAN 超融合集群,通过将主机的本地硬盘进行聚合后,最终形成一个统一的存储池供工作负载使用。VCF 管理域集群就是一个标准的 vSAN HCI 集群,关于 vSAN HCI 集群的详细创建过程这里就不再赘述了,有需要可以查看之前这篇(
VMware Cloud Foundation Part 07:管理工作负载域中的主机和集群。
)文章,下面只陈述创建 vSAN HCI 集群需要注意的地方,以及 vSAN HCI 集群之间远程挂载数据存储的过程。

添加用于 vSAN HCI 集群的服役主机时,主机的 vSAN 类型选择“vSAN HCI”,根据挂载远程数据存储要求选择 vSAN OSA/ESA 架构。

由于 VCF 管理域初始集群是 vSAN ESA 架构,所以需要添加 3 台用于 vSAN HCI ESA 集群的 ESXi 主机。

添加 vSAN 集群时需要选择 vSAN ESA 架构并选择 vSAN 集群类型为“vSAN HCI”。

最终,vSAN HCI 集群(vcf-mgmt01-cluster03)创建成功,如下图所示。

点击该集群左边三个点处,选择“挂载远程数据存储”。

可选择的远程 vSAN 数据存储,勾选后,点击下一步。

新创建的 vSAN HCI 集群当前没有远程 vSAN 容量,如果执行本次挂载远程数据存储后,新的远程 vSAN 容量如下图所示。

远程 vSAN 数据存储挂载进行中......

已成功挂载远程 vSAN 数据存储。

点击该集群,查看挂载情况。

服务器端集群查看挂载情况,现在有两个客户端集群挂载了自己。

其实,对于 vSAN HCI 集群来说,支持互相挂载远程 vSAN 数据存储,在 VCF 管理域集群上点击“挂载远程数据存储”。

可以选择的远程 vSAN 数据存储。

如果挂载了此远程 vSAN 数据存储,可以增加的远程 vSAN 容量。

挂载成功后,查看 vSAN 配置。

点击进入集群,查看挂载情况。

在 vCenter Server(vSphere Client)查看 vSAN HCI 集群的状态。

查看 vSAN HCI 集群挂载的远程 vSAN 数据存储。

三、vSAN Max 集群

vSAN Max 是基于 vSAN ESA 的全新分解存储架构,通过充分利用 vSAN ESA 架构的各种优势,并单独针对存储数据的处理任务进行各种优化,专门用来向 vSAN 计算集群和 vSAN HCI 集群提供远程数据存储。有关更多内容和细节请查看这篇(
创建vSAN Max集群并配置挂载远程数据存储。
)文章。

导航到 SDDC Manager->清单->主机,点击“服役主机”添加用于 vSAN Max 集群的 ESXi 主机。选择“vSAN”存储类型并选择 vSAN 类型为“vSAN Max”,其他选项可根据情况自行进行调整,然后完成服役主机添加工作流。

使用上述同样的方式添加 3 台主机以用于 vSAN Max 集群,主机服役后会显示在未分配的主机列表当中,如下图所示。

导航到 SDDC Manager->清单->工作负载域,在管理域左边三个点处点击“添加集群”。

存储类型选择“vSAN”并勾选“启用 vSAN ESA”,点击开始。

设置 vSAN Max 集群的名称(vcf-mgmt01-cluster04),点击下一步。

选择 vSAN 集群类型为“vSAN Max”,点击下一步。

选择用于 vSAN Max 集群的 ESXi 主机,点击下一步。

配置 vSAN Max 集群主机的虚拟交换机,选择创建自定义交换机配置,过程同样也忽略了,点击下一步。

配置用于 vSAN Max 集群的许可证,点击下一步。

检查配置,点击完成。

如果一切顺利,vSAN Max 集群创建成功。

先在 vSAN 计算集群上挂载远程 vSAN Max 集群的数据存储。

选择 vSAN Max 集群的数据存储,下面的 vSAN HCI 集群的数据存储已经挂载了所以是灰色的,点击下一步。

因为已经挂载了 vSAN HCI 的数据存储,所以当前的远程 vSAN 容量有 3 TB,如果执行了此挂载任务,则新的远程 vSAN 容量可以达到 4 TB,如下图所示,点击完成。

挂载成功后,vSAN Max 集群的 vSAN 配置状态已变成服务器端。

点击 vSAN 计算集群查看挂载情况。

点击 vSAN Max 集群查看挂载情况。

同样,vSAN Max 集群也可以被 vSAN HCI 集群进行挂载使用。

点击 vSAN HCI 集群查看挂载情况。

请注意,vSAN Max 集群只能充当服务器端,不能充当客户端,也就是说只能被其他类型的集群挂载使用,不能去挂载别的远程 vSAN 数据存储。同时,挂载了远程 vSAN 数据存储的集群不能被删除,只有当远程数据存储被卸载了以后才能进行删除。

在 vCenter Server(vSphere Client)查看 vSAN Max 集群的状态。

查看 vSAN Max 集群所挂载的 vSAN 客户端集群。

如果你看过这篇(
创建vSAN Max集群并配置挂载远程数据存储。
)文章,那应该知道挂载远程 vSAN 数据存储是支持跨 vCenter Server 实例共享的,这对于 VMware Cloud Foundation 解决方案来说,就是跨工作负载域共享,但是目前说是只支持在同一工作负载域中的不同集群之间远程共享数据存储,我看了一下,这在 SDDC Manager 中好像确实不能,但是从技术上讲,在工作负载域的 vCenter Server(vSphere Client)中配置跨 vCenter Server 挂载远程 vSAN 数据存储应该是可行的,这仅限于 VI 工作负载域,因为管理工作负载域只有一个。当然,VMware 没有表明那肯定不受官方支持,所以还是严格按照文档指引来使用吧!

在当今数字化高速发展的时代,B 端产品经理作为企业数字化转型的关键推动者,肩负着重大的责任。不仅要深入了解企业的业务需求,还要紧跟技术发展的步伐,为企业提供高效、创新的解决方案。那么,未来几年,B 端产品领域将会呈现出哪些发展趋势呢?

B 端产品未来几年的发展趋势

一、人工智能与机器学习的深度融合

人工智能(AI)和机器学习(ML)已经在 B 端产品中崭露头角,未来几年,它们的应用将更加广泛和深入。

  • 智能客服与自动化服务

智能客服系统将不断升级,能够更准确地理解客户问题,并提供个性化的解决方案。通过机器学习算法,智能客服可以不断学习和改进,提高回答问题的准确率和效率。同时,自动化服务也将在更多的业务场景中得到应用,例如自动化订单处理、自动化库存管理等,大大提高企业的运营效率。

  • 数据分析与预测

AI 和 ML 技术将在数据分析领域发挥更大的作用。可以利用这些技术对大量的业务数据进行分析,挖掘出有价值的信息,为企业决策提供支持。例如,通过预测分析,企业可以提前预测市场需求、客户行为等,从而优化生产计划、营销策略等。

  • 智能推荐与个性化服务

基于用户行为和偏好的智能推荐系统将成为 B 端产品的重要组成部分。通过机器学习算法,产品可以为用户提供个性化的服务和推荐,提高用户体验和满意度。例如,在企业资源规划(ERP)系统中,根据用户的历史操作和偏好,为用户推荐相关的业务流程和功能。

二、云计算与容器化技术的持续发展

云计算和容器化技术已经成为企业数字化转型的重要支撑,未来几年,它们将继续发挥重要作用。

  • 多云战略与混合云部署

随着企业对云计算的需求不断增加,多云战略将成为越来越多企业的选择。考虑如何支持企业的多云战略,实现不同云平台之间的无缝集成和管理。同时,混合云部署也将越来越普遍,企业可以根据自身的业务需求和安全要求,灵活地选择公有云、私有云和本地部署的组合。

  • 容器化技术与微服务架构

容器化技术和微服务架构将进一步推动 B 端产品的发展。容器化技术可以提高应用的部署效率和可移植性,微服务架构可以提高系统的可扩展性和灵活性。需要了解这些技术的特点和优势,为企业设计和开发更加高效、灵活的产品。

  • 云原生应用与开发

云原生应用是指基于云计算技术构建的应用,具有高可用、高弹性、易扩展等特点。未来几年,云原生应用将成为 B 端产品的主流发展方向。需要掌握云原生技术,为企业开发出更加符合云时代需求的产品。

三、用户体验与设计的重要性日益凸显

在 B 端产品领域,用户体验和设计的重要性也越来越受到重视。未来几年,产品经理需要更加注重用户体验和设计,为用户提供更加优质的产品和服务。

  • 以用户为中心的设计理念

深入了解用户的需求和痛点,以用户为中心进行产品设计。通过用户调研、用户测试等方法,不断优化产品的用户体验,提高用户满意度。同时,产品设计也需要更加注重简洁性和易用性,让用户能够快速上手并高效地完成工作。

  • 个性化用户体验

随着用户需求的不断变化,个性化用户体验将成为 B 端产品的重要发展方向。通过用户画像、数据分析等方法,了解用户的个性化需求,为用户提供个性化的产品和服务。例如,在企业管理软件中,根据不同用户的角色和权限,为用户定制个性化的工作界面和功能菜单。

  • 设计思维与创新

设计思维将成为 B 端产品经理必备的技能之一。通过设计思维,产品经理可以更好地理解用户需求,提出创新的解决方案。同时,创新也是 B 端产品发展的关键,产品经理需要不断探索新的技术和业务模式,为企业带来更多的价值。

四、数据安全与隐私保护的挑战与机遇

随着数字化程度的不断提高,数据安全和隐私保护也成为企业面临的重要挑战。未来几年,更加重视数据安全和隐私保护,为企业提供更加安全可靠的产品和服务。

  • 数据安全管理

需要建立完善的数据安全管理体系,包括数据加密、访问控制、数据备份等措施,确保企业数据的安全。同时,产品也需要符合相关的安全标准和法规,如 GDPR、HIPAA 等。

  • 隐私保护技术

隐私保护技术将不断发展,例如差分隐私、同态加密等。了解这些技术的特点和应用场景,为企业提供更加有效的隐私保护解决方案。同时,产品也需要在设计和开发过程中充分考虑用户隐私保护的需求,确保用户数据的安全和隐私。

  • 安全意识培训

数据安全和隐私保护不仅仅是技术问题,也是人的问题。加强用户的安全意识培训,提高用户对数据安全和隐私保护的重视程度。同时,企业也需要建立健全的安全管理制度,加强内部安全管理。

五、行业垂直化与专业化发展

随着市场竞争的加剧,B 端产品将越来越向行业垂直化和专业化方向发展。未来几年,更加深入地了解行业特点和业务需求,为企业提供更加专业的产品和服务。

  • 行业解决方案

针对不同的行业特点,开发出行业专属的解决方案。例如,在医疗行业,开发出符合医疗行业标准和法规的医疗管理软件;在金融行业,开发出安全可靠的金融交易系统等。这些行业解决方案将更加贴近用户需求,提高用户满意度。

  • 专业化功能模块

除了行业解决方案,B 端产品也将越来越注重专业化功能模块的开发。例如,在企业资源规划(ERP)系统中,开发出针对不同行业的财务管理模块、供应链管理模块等。这些专业化功能模块将更加满足用户的特定需求,提高产品的竞争力。

  • 行业合作与生态建设

加强与行业内其他企业的合作,共同打造行业生态。通过合作,企业可以共享资源、优势互补,提高产品的质量和服务水平。同时,行业生态的建设也将促进整个行业的发展,为企业带来更多的机遇。

六、敏捷开发与持续交付的普及

敏捷开发和持续交付已经成为软件开发的主流方法,未来几年,它们将在 B 端产品开发中得到更加广泛的应用。

  • 敏捷开发方法

敏捷开发方法强调快速迭代、用户参与和团队协作。采用敏捷开发方法,提高产品开发的效率和质量。同时,产品经理也需要与开发团队、测试团队等密切合作,确保产品能够按时交付。

  • 持续交付与部署

持续交付和部署是指将产品的开发、测试和部署过程自动化,实现快速交付和部署。建立完善的持续交付和部署体系,提高产品的交付速度和稳定性。同时,产品也需要支持自动化测试和部署,确保产品的质量和安全性。

  • 敏捷项目管理

敏捷项目管理强调项目的灵活性和适应性。采用敏捷项目管理方法,对项目进行有效的管理和控制。同时,产品经理也需要与项目团队成员密切合作,及时解决项目中出现的问题,确保项目能够按时完成。

七、总结

未来几年,B 端产品领域将面临着诸多挑战和机遇。人工智能与机器学习的深度融合、云计算与容器化技术的持续发展、用户体验与设计的重要性日益凸显、数据安全与隐私保护的挑战与机遇、行业垂直化与专业化发展以及敏捷开发与持续交付的普及等趋势,将对 B 端产品经理提出更高的要求。作为 B 端产品经理,我们需要不断学习和掌握新的技术和方法,深入了解用户需求和行业特点,为企业提供更加高效、创新、安全可靠的产品和服务。只有这样,我们才能在激烈的市场竞争中立于不败之地,为企业的数字化转型和发展做出更大的贡献。