首页 文章 精选 留言 我的

精选列表

搜索[基础搭建],共10000篇文章
优秀的个人博客,低调大师

使用MASA全家桶从零开始搭建IoT平台(二)设备注册

前言 我们不希望任何设备都可以接入我们的IoT平台,所以一个设备正常的接入流程是这样的, 1、上位机软件通过串口或其他方式读取设备的唯一标识码UUID。 2、上位机调用IoT后台接口,发送UUID和ProductID。 3、后台接口判断设备是否注册过,如果没有注册过,就根据ProductID并按照一定规律生成DeviceName和Password通过接口返回给上位机软件。 4、上位机软件通过串口将接口返回的数据写入设备。 一、设备注册流程 这里主要涉及四个概念 1、UUID(设备唯一ID,一般为设备主控板编号) 2、ProductID(设备所属产品ID,在IoT后台定义) 3、DeviceName(设备在IoT平台或MQTT的名称,该名称大多与产品相关) 4、Password(设备连接MQTT的密码) 二、MQTT注册 1.在EMQX中添加认证方式 选择Built-in Database方式,内置数据库进行密码认证 账号类型选择username,加密方式和加盐方式可以保持默认。 点击创建后可以在认证菜单中看到新建的认证方式,状态为:已连接。 我们点击用户管理->添加 可以手动创建用户 这里的场景我们是通过上位机调用IoT后端,IoT接口内部调用EMQX接口来实现自动创建用户的 2.创建Api Key 调用接口需要认证,这里我们使用Api key的方式,我们在系统设置->API密钥中创建一个API密钥 Secret Key 只有创建的时候才会显示明文,我们需要记录下API Key 和 Secret Key 3.调用接口创建用户 我们在浏览器打开EMQX 的RestAPI swagger http://localhost:18083/api-docs/index.html 我们可以通过这个接口来创建用户,这里的Authenticator ID 就是我们上面创建的内置数据库 Password Based的ID, 这个ID的获取通过下面的authentication方法获取 我们在认证中直接使用API Key 和 Secret Key,接口返回Id:password_based:built_in_database 调用authentication的Post接口,在 id字段输入:password_based:built_in_database,Request body中输入设备的user_id和password即可成功创建用户。 我们在Deshboard的界面中也可以看到刚刚创建的用户 三、测试设备连接 我们使用MQTTX来模拟客户端设备通过mqtt协议连接到EMQX,新建连接,填写地址、端口、和刚刚通过Api创建用户名密码。 点击连接、发现设备已经可以正常连接mqtt了。 在Dashboard中也可以看到当前连接的客户端ID等信息。 四、编写代码 在MASA.IoT.WebApi项目种添加DeviceController控制器并添加DeviceRegAsync方法用于设备注册, 设备如果没有注册过(UUID 数据库不存在),那么会根据ProductCode按照规律生成设备名称,名称以该产品供应商编号开头,后跟时间和序号。然后向EMQX添加设备,并同时存储到数据库中。 如果设备已经注册过,那么直接从数据库取出设备注册信息返回。 代码编写相对简单,不过多赘述。 //DeviceController namespace MASA.IoT.WebApi.Controllers { [Route("api/[controller]")] [ApiController] public class DeviceController : ControllerBase { private readonly IDeviceHandler _deviceHandler; public DeviceController(IDeviceHandler deviceHandler) { _deviceHandler = deviceHandler; } [HttpPost] public async Task<DeviceRegResponse> DeviceRegAsync(DeviceRegRequest request) { return await _deviceHandler.DeviceRegAsync(request); } } } //DeviceHandler using MASA.IoT.WebApi.Contract; using MASA.IoT.WebApi.IHandler; using MASA.IoT.WebApi.Models.Models; using Microsoft.EntityFrameworkCore; namespace MASA.IoT.WebApi.Handler { public class DeviceHandler : IDeviceHandler { private readonly MASAIoTContext _ioTDbContext; private readonly IMqttHandler _mqttHandler; public DeviceHandler(MASAIoTContext ioTDbContext, IMqttHandler mqttHandler) { _ioTDbContext = ioTDbContext; _mqttHandler = mqttHandler; } /// <summary> /// 注册设备 /// </summary> /// <param name="request"></param> /// <returns> /// 设备注册信息 /// </returns> public async Task<DeviceRegResponse> DeviceRegAsync(DeviceRegRequest request) { var productInfo = await _ioTDbContext.IoTProductInfo.FirstOrDefaultAsync(o => o.ProductCode == request.ProductCode); if (productInfo == null) { return new DeviceRegResponse { Succeed = false, ErrMsg = "ProductCode not found" }; } var deviceRegInfo = await GetDeviceRegInfoAsync(request); if (deviceRegInfo != null) //已经注册过 { return deviceRegInfo; } else //没有注册过 { var deviceName = await GenerateDeviceNameAsync(productInfo.SupplyNo, request.ProductCode, request.UUID); var password = Guid.NewGuid().ToString("N"); var addDeviceResponse = await _mqttHandler.DeviceRegAsync(deviceName, password); if (addDeviceResponse.user_id == deviceName) //注册成功 { deviceRegInfo = new DeviceRegResponse { DeviceName = deviceName, Password = password, Succeed = true, ErrMsg = string.Empty }; await _ioTDbContext.IoTDeviceInfo.AddAsync(new IoTDeviceInfo { Id = Guid.NewGuid(), DeviceName = deviceName, Password = password, ProductInfoId = productInfo.Id, }); await _ioTDbContext.SaveChangesAsync(); return deviceRegInfo; } return new DeviceRegResponse { Succeed = false, ErrMsg = addDeviceResponse.message }; } } /// <summary> /// 获取设备注册信息 /// </summary> /// <param name="request"></param> /// <returns> /// 设备已经注册返回设备注册信息,没有注册过返回null /// </returns> private async Task<DeviceRegResponse?> GetDeviceRegInfoAsync(DeviceRegRequest request) { var deviceware = await _ioTDbContext.IoTDevicewares.FirstOrDefaultAsync(o => o.ProductCode == request.ProductCode && o.UUID == request.UUID); if (deviceware == null) { return null; } else { var deviceInfo = await _ioTDbContext.IoTDeviceInfo.FirstAsync(o => o.DeviceName == deviceware.DeviceName); return new DeviceRegResponse { DeviceName = deviceInfo.DeviceName, Password = deviceInfo.Password, Succeed = true, ErrMsg = string.Empty }; } } /// <summary> /// 生成设备名称 /// </summary> /// <param name="supplyNo"></param> /// <param name="productCode"></param> /// <param name="uuid"></param> /// <returns> /// 设备Mqtt名称 /// </returns> private async Task<string> GenerateDeviceNameAsync(string supplyNo, string productCode, string uuid) { var lastDeviceware = await _ioTDbContext.IoTDevicewares.Where(o => o.ProductCode == productCode).OrderByDescending(o => o.CreationTime).FirstOrDefaultAsync(); var newDeviceware = new IoTDevicewares { Id = Guid.NewGuid(), UUID = uuid, ProductCode = productCode, CreationTime = DateTime.Now }; if (lastDeviceware != null && lastDeviceware.DeviceName.StartsWith(supplyNo + DateTime.Today.ToString("yyyyMMdd"))) { newDeviceware.DeviceName = (long.Parse(lastDeviceware.DeviceName) + 1).ToString(); } else { newDeviceware.DeviceName = supplyNo + DateTime.Today.ToString("yyyyMMdd") + "0001"; } await _ioTDbContext.IoTDevicewares.AddAsync(newDeviceware); await _ioTDbContext.SaveChangesAsync(); return newDeviceware.DeviceName; } } } 这里生成设备名称用了一个简单的算法 // MqttHandler using Flurl.Http; using MASA.IoT.WebApi.Contract.Mqtt; using MASA.IoT.WebApi.IHandler; using Microsoft.Extensions.Options; using System.Net; namespace MASA.IoT.WebApi.Handler { public class MqttHandler : IMqttHandler { private readonly AppSettings _appSettings; public MqttHandler(IOptions<AppSettings> settings) { _appSettings = settings.Value; } public async Task<AddDeviceResponse> DeviceRegAsync(string deviceName,string password) { var url = $"{_appSettings.MqttSetting.Url}/api/v5/authentication/password_based:built_in_database/users"; var response = await url.WithBasicAuth(_appSettings.MqttSetting.ApiKey, _appSettings.MqttSetting.SecretKey).AllowAnyHttpStatus().PostJsonAsync(new AddDeviceRequest { user_id = deviceName, password = password, } ); if (response.StatusCode is (int)HttpStatusCode.Created or (int)HttpStatusCode.BadRequest or (int)HttpStatusCode.NotFound) { return await response.GetJsonAsync<AddDeviceResponse>(); } else { throw new UserFriendlyException(await response.GetStringAsync()); } } } } 总结 以上就是本文要讲的内容,本文介绍了通过账号密码的方式通过接口在EMQX中创建用户,并连接EMQX的过程,EMQX支持的认账方式还有很多,例如JWT认证方式可以授权一次性密码认证,可以控制认证的有效期,我们在后面的章节具体应用中会进行说明。 完整代码在这里:https://github.com/sunday866/MASA.IoT-Training-Demos 如果你对我们的 MASA 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们 WeChat:MasaStackTechOps QQ:7424099

优秀的个人博客,低调大师

5分钟实现用docker搭建Redis集群模式和哨兵模式

如果让你为开发、测试环境分别搭一套哨兵和集群模式的redis,你最快需要多久,或许你需要一天?2小时?事实是可以更短。 是的,你已经猜到了,用docker部署,真的只需要十几分钟。 一.准备工作 拉取redis镜像 运行如下命令: docker pull redis 该命令拉取的镜像是官方镜像,当然你可以搜索其他的镜像,这里不做深入 查看镜像情况: 二.部署redis哨兵主从模式 什么是哨兵模式?--请自行百度 1、什么是docker compose? Docker Compose 可以理解为将多个容器运行的方式和配置固化下来! 就拿最简单的例子来说吧,如果我们要为我们的应用容器准备一个 MySQL 容器和一个 Redis 容器,那么在每次启动时,我们先要将 MySQL 容器和 Redis 容器启动起来,再将应用容器运行起来。这其中还不要忘了在创建应用容器时将容器网络连接到 MySQL 容器和 Redis 容器上,以便应用连接上它们并进行数据交换。 这还不够,如果我们还对容器进行了各种配置,我们最好还得将容器创建和配置的命令保存下来,以便下次可以直接使用。 针对这种情况,我们就不得不引出在我们开发中最常使用的多容器定义和运行软件,也就是 Docker Compose 了。 2、编写reids主从docker-compose.yml version: '3.7' services: master: image: redis container_name: redis-master restart: always command: redis-server --requirepass redispwd --appendonly yes ports: - 6379:6379 volumes: - ./data1:/data slave1: image: redis container_name: redis-slave-1 restart: always command: redis-server --slaveof redis-master 6379 --requirepass redispwd --masterauth redispwd --appendonly yes ports: - 6380:6379 volumes: - ./data2:/data slave2: image: redis container_name: redis-slave-2 restart: always command: redis-server --slaveof redis-master 6379 --requirepass redispwd --masterauth redispwd --appendonly yes ports: - 6381:6379 volumes: - ./data3:/data 名词解释: 3、启动主从redis 进入redis对应的docker-compose.yml的目录,执行命令: docker-compose up -d -d表示后台运行 使用命令docker ps命令查看启动结果: 出现截图所示,表示运行成功 4.编写哨兵docker-compose.yml version: '3.7' services: sentinel1: image: redis container_name: redis-sentinel-1 restart: always ports: - 26379:26379 command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel1.conf:/usr/local/etc/redis/sentinel.conf sentinel2: image: redis container_name: redis-sentinel-2 restart: always ports: - 26380:26379 command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel2.conf:/usr/local/etc/redis/sentinel.conf sentinel3: image: redis container_name: redis-sentinel-3 ports: - 26381:26379 command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel3.conf:/usr/local/etc/redis/sentinel.conf networks: default: external: name: redis_default 5.编写哨兵sentinel.conf # 自定义集群名,其中172.19.0.3 为 redis-master 的 ip,6379 为 redis-master 的端口,2 为最小投票数(因为有 3 台 Sentinel 所以可以设置成 2) port 26379 dir /tmp sentinel monitor mymaster 172.19.0.3 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel auth-pass mymaster redispwd sentinel failover-timeout mymaster 180000 sentinel deny-scripts-reconfig yes 将上述文件分别拷贝3份分别命名为sentinel1.conf、sentinel2.conf、sentinel3.conf与docker-compose.yml中的配置文件对应,然后放置和哨兵的docker-compose.yml在同一目录 6.启动哨兵 进入哨兵docker-compose.yml所在目录,执行命令: docker-compose up -d 查看容器,可以看到哨兵和主从redis都起来了 6.1哨兵启动日志 上述日志中可以看出,哨兵监听master和slave节点 6.2关掉master节点 通过命令停止redis的master节点 docker stop redis-master 通过上述日志,我们可以看到sdown,odown,他们是什么意思呢? sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机 odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机 然后就是开始选举,从日志可以看出两个哨兵选择了同一个slave节点,这时候满足了我们配置最小投票数,那么这台slave就被选为新的master。 6.3重开master节点 上述日志表明哨兵检测到原master重新启动,将原master节点变成新master的从节点 三.部署redis集群模式 1、创建目录和文件 ├── docker-compose.yml ├── redis-6371 │ ├── conf │ │ └── redis.conf │ └── data ├── redis-6372 │ ├── conf │ │ └── redis.conf │ └── data ├── redis-6373 │ ├── conf │ │ └── redis.conf │ └── data ├── redis-6374 │ ├── conf │ │ └── redis.conf │ └── data ├── redis-6375 │ ├── conf │ │ └── redis.conf │ └── data └── redis-6376 ├── conf │ └── redis.conf └── data 2、redis.conf 配置文件 port 6371 cluster-enabled yes cluster-config-file nodes-6371.conf cluster-node-timeout 5000 appendonly yes protected-mode no requirepass 1234 masterauth 1234 cluster-announce-ip 10.12.12.10 # 这里是宿主机IP cluster-announce-port 6371 cluster-announce-bus-port 16371 每个节点的配置只需改变端口。 3、docker-compose 配置文件 version: "3" # 定义服务,可以多个 services: redis-6371: # 服务名称 image: redis # 创建容器时所需的镜像 container_name: redis-6371 # 容器名称 restart: always # 容器总是重新启动 volumes: # 数据卷,目录挂载 - ./redis-6371/conf/redis.conf:/usr/local/etc/redis/redis.conf - ./redis-6371/data:/data ports: - 6371:6371 - 16371:16371 command: redis-server /usr/local/etc/redis/redis.conf redis-6372: image: redis container_name: redis-6372 volumes: - ./redis-6372/conf/redis.conf:/usr/local/etc/redis/redis.conf - ./redis-6372/data:/data ports: - 6372:6372 - 16372:16372 command: redis-server /usr/local/etc/redis/redis.conf redis-6373: image: redis container_name: redis-6373 volumes: - ./redis-6373/conf/redis.conf:/usr/local/etc/redis/redis.conf - ./redis-6373/data:/data ports: - 6373:6373 - 16373:16373 command: redis-server /usr/local/etc/redis/redis.conf redis-6374: image: redis container_name: redis-6374 restart: always volumes: - ./redis-6374/conf/redis.conf:/usr/local/etc/redis/redis.conf - ./redis-6374/data:/data ports: - 6374:6374 - 16374:16374 command: redis-server /usr/local/etc/redis/redis.conf redis-6375: image: redis container_name: redis-6375 volumes: - ./redis-6375/conf/redis.conf:/usr/local/etc/redis/redis.conf - ./redis-6375/data:/data ports: - 6375:6375 - 16375:16375 command: redis-server /usr/local/etc/redis/redis.conf redis-6376: image: redis container_name: redis-6376 volumes: - ./redis-6376/conf/redis.conf:/usr/local/etc/redis/redis.conf - ./redis-6376/data:/data ports: - 6376:6376 - 16376:16376 command: redis-server /usr/local/etc/redis/redis.conf 编写完成后使用docker-compose up -d启动容器 ,这里没有使用主机模式(host),而是使用 NAT 模式,因为主机模式可能导致外部客户端无法连接。 4、进入容器,创建集群 上面只是启动了 6 个 Redis 实例,并没有构建成 Cluster 集群。 执行docker exec -it redis-6371 bash进入一个 Redis 节点容器,随便哪个都行。 继续执行以下命令创建集群: # 集群创建命令 redis-cli -a 1234 --cluster create 10.35.30.39:6371 10.35.30.39:6372 10.35.30.39:6373 10.35.30.39:6374 10.35.30.39:6375 10.35.30.39:6376 --cluster-replicas 1 # 执行过后会有以下输出 Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 10.35.30.39:6375 to 10.35.30.39:6371 Adding replica 10.35.30.39:6376 to 10.35.30.39:6372 Adding replica 10.35.30.39:6374 to 10.35.30.39:6373 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: e9a35d6a9d203830556de89f06a3be2e2ab4eee1 10.35.30.39:6371 slots:[0-5460] (5461 slots) master M: 0c8755144fe6a200a46716371495b04f8ab9d4c8 10.35.30.39:6372 slots:[5461-10922] (5462 slots) master M: fcb83b0097d2a0a87a76c0d782de12147bc86291 10.35.30.39:6373 slots:[10923-16383] (5461 slots) master S: b9819797e98fcd49f263cec1f77563537709bcb8 10.35.30.39:6374 replicates fcb83b0097d2a0a87a76c0d782de12147bc86291 S: f4660f264f12786d81bcf0b18bc7287947ec8a1b 10.35.30.39:6375 replicates e9a35d6a9d203830556de89f06a3be2e2ab4eee1 S: d2b9f265ef7dbb4a612275def57a9cc24eb2fd5d 10.35.30.39:6376 replicates 0c8755144fe6a200a46716371495b04f8ab9d4c8 Can I set the above configuration? (type 'yes' to accept): yes # 这里输入 yes 并回车 确认节点 主从身份 以及 哈希槽的分配 >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join . >>> Performing Cluster Check (using node 10.35.30.39:6371) M: e9a35d6a9d203830556de89f06a3be2e2ab4eee1 10.35.30.39:6371 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 0c8755144fe6a200a46716371495b04f8ab9d4c8 10.35.30.39:6372 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: b9819797e98fcd49f263cec1f77563537709bcb8 10.35.30.39:6374 slots: (0 slots) slave replicates fcb83b0097d2a0a87a76c0d782de12147bc86291 M: fcb83b0097d2a0a87a76c0d782de12147bc86291 10.35.30.39:6373 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: f4660f264f12786d81bcf0b18bc7287947ec8a1b 10.35.30.39:6375 slots: (0 slots) slave replicates e9a35d6a9d203830556de89f06a3be2e2ab4eee1 S: d2b9f265ef7dbb4a612275def57a9cc24eb2fd5d 10.35.30.39:6376 slots: (0 slots) slave replicates 0c8755144fe6a200a46716371495b04f8ab9d4c8 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. 看到上面的输出即为 Cluster 集群配置完成。且为 3 主 3 从。 总结: 以上就是通过docker compose方式部署哨兵模式和集群模式的全过程,redis部署在docker中,适用于本地、开发、测试等环境,生产环境请慎用,除非你对docker有很强的掌控力。 关注我,下一篇继续

优秀的个人博客,低调大师

快速无副作用搭建Java 17环境并玩转Record特性

Java 17现在已经发布,不少同学蠢蠢欲试,但是又担心配置新的JDK会影响现在的项目环境。今天介绍一个项目级别的JDK配置方法。让你先人一步快速入门Java 17,同时也不会影响原有项目。 项目快速集成Java 17 在发文前,亚马逊的Corretto JDK 17 、Zulu JDK 17 都已经加入了豪华午餐。 选完就可以下载Java 17的JDK了。可能是因为刚发布的缘故,实在太慢了。所以我直接到Open JDK 的官网的JDK17下载了一份。解压到Windows当前用户文件夹路径下(我的是C:\Users\n1\.jdks),之所以解压到.jdks下是因为IDEA的下载目标文件夹就是这个文件夹,方便IDEA自动检出。 这里不需要重新配置Java环境变量,都是项目级别的Java版本控制,不会对你的其它项目造成影响。 然后新建一个Maven项目(也可以是普通项目或者Gradle项目),这个时候你还不能愉快地玩耍。你需要确定两件事情。 语言级别 调整JDK的语言级别为Java 17 ,在IDEA下按快捷键 Ctrl+Alt+Shift+S 呼出下面的对话框并将Language Level修改为17。 字节码版本 编译器的字节码版本也需要调整为17。IDEA中按下快捷键 Ctrl+Alt+S 在图示中的位置进行修改。 Record Class 搞定了环境配置后,我们开始试一试一个最直观的、也相当有用的语法糖Record。 准确地说这不属于Java 17的新特性,最早在Java 14 中出现,在Java 16中转为正式特性。不过作为LTS版本,这依然是很重要的一个概念。 我们直观一些,一个数据类传统的写法是: public class MyRecord { private final String username; private final Integer age; public MyRecord(String username, Integer age) { this.username = username; this.age = age; } public String username() { return username; } public Integer age() { return age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyRecord oldRecord = (MyRecord) o; return Objects.equals(username, oldRecord.username) && Objects.equals(age, oldRecord.age); } @Override public int hashCode() { return Objects.hash(username, age); } @Override public String toString() { return "MyRecord[" + "username='" + username + '\'' + ", age=" + age + ']'; } } 用Record就可以简化为: public record MyRecord(String username,Integer age) { } 这样大大减少了一些模板代码,让逻辑更加清晰简单。 Record 是不可变的 Record被用来设计传输不可变的数据。从上面的例子可以看到,一个Record类被初始化后里面的属性是不能改变的,没有Setter方法而是通过全参数构造来初始化数据,天然线程安全。 Record的超类 所有用Record关键字声明的类都是java.lang.Record的子类,这一点有点像枚举。 public abstract class Record { protected Record() {} @Override public abstract boolean equals(Object obj); @Override public abstract int hashCode(); @Override public abstract String toString(); } 从这里也可以看出所有Record的实现都覆写了equals、hashCode、toString三个方法。 如何判断一个类是Record类? 传统方法: Record.class.isAssignableFrom(MyRecord.class) JDK提供了一个新的方法来解决这个问题: MyRecord.class.isRecord() 值得一提的是Class类还提供了getRecordComponents来获取Record类的成员属性信息。 RecordComponent[] recordComponents = MyRecord.class.getRecordComponents(); Record无法使用extends关键字 由于Record类的唯一的隐式超类是java.lang.Record,Java不支持多继承,使用 extends 显式定义会导致编译错误。 无法定义额外的成员变量 Record类的成员变量只能通过构造声明。所以下面这种写法是错误的: public record MyRecord(String username,Integer age) { privite String gender; } 但是你可以在Record类中定义静态变量。 定义方法时需要小心 定义方法比较开放,但是请确保你定义的方法不会破坏Record不可变的含义。不推荐定义Setter方法。 另外注意Record类的Getter方法不是setXXXX格式的。 使用注解 唯一需要注意的是,在Record类的成员变量上使用注解可能会作用的Getter方法上。就像这样: public record MyRecord(@Deprecated String username,Integer age) { } 编译后: public record MyRecord(String username, Integer age) { public MyRecord(@Deprecated String username, Integer age) { this.username = username; this.age = age; } public String getUsername() { return this.username; } /** @deprecated */ @Deprecated public String username() { return this.username; } public Integer age() { return this.age; }} 具体的作用域需要根据注解上的@Target元注解的定义域来判定。 总结 今天介绍了如何快速集成Java 17,而且不影响已有的项目。借着这个机会也对Record类进行了介绍和讲解,希望在你初次接触这种新定义的时候能够帮助你。原创不易,还请多多关注、点赞、再看、转发。 关注公众号:Felordcn获取更多资讯 个人博客:https://felord.cn

优秀的个人博客,低调大师

手把手教你搭建高逼格监控平台,动起来吧

2 --> 涉及软件 prometheus + Grafana + exporter等,文末有下载地址,亲测可用哦。可能会有人问这些是什么玩意?莫急,咱一个个来认识他。 环境模拟 监控平台所在服务器 1.1.1.1 (假设的IP) 被监控的web服务器 2.2.2.2(假设的IP) 被监控的mysql服务器 3.3.3.3(假设的IP) 安装 prometheus Prometheus是一个开源的系统监控和警报工具包,最初是在SoundCloud上构建的。自2012年成立以来,许多公司和组织都采用了Prometheus,该项目拥有非常活跃的开发人员和用户社区。 在 Prometheus + Grafana 的体系架构下,Prometheus 相当于一个注册中心。 以下操作在监控平台所在服务器 1.1.1.1 (假设的IP)上。 1.1 下载 prometheus wget https://github.com/prometheus/prometheus/releases/download/v2.27.1/prometheus-2.27.1.linux-amd64.tar.gz 1.2 解压 tar xvfz prometheus-2.27.1.linux-amd64.tar.gz 1.3 用 Vim 打开 prometheus.yml查看配置文件 这一步暂时不要修改改文件,打开看一眼就行了 1.4 启动 prometheus nohup ./prometheus --config.file=prometheus.yml & 通过浏览器输入:http://your_ip:9090, 假如能够跳转到如下页面,说明 prometheus 安装启动成功。 安装 node_exporter node_exporter 可以看作是 prometheus 的一个监控插件,用于监控服务器的系统指标。也就是说你要监控哪些服务器,就需要在这些服务上都安装并启动node_exporter。 此处我们要在监控平台所在服务器 1.1.1.1 (假设的IP)、被监控的web服务器 2.2.2.2(假设的IP)、被监控的mysql服务器 3.3.3.3(假设的IP)三个服务器上安装,安装方法都一样。 1.1 下载 node_exporter wget https://github.com/prometheus/node_exporter/releases/download/v1.1.2/node_exporter-1.1.2.linux-amd64.tar.gz 1.2 解压 tar xvfz node_exporter-1.1.2.linux-amd64.tar.gz 1.3 启动 node_exporter 默认端口是:9100 nohup ./node_exporter 通过浏览器输入:http://your_ip:9100/metrics 假如能够跳转到如下页面,则说明 node_exporter 安装启动成功。 1.4 修改 prometheus.yml 配置 这里我们需要修改一下 prometheus.yml 配置,把要监控的服务加进来。回prometheus的目录下,通过vim prometheus.yml 来修改 prometheus.yml 配置文件。 如上图,在末尾加上,一定要注意缩进等格式,不然会报错,最好的方法就是照着配置文件中已有的格式对下,缩进啊空格啊,该有的必须有,但不能有的觉得不要多!!! 这里我们使用的是file_sd_configs动态加载job的功能,参数files就是指定了我们要加载啥yml文件在啥地方,这里使用了*做通配符,加载所有以yml结尾的文件。 看到了吧,在指定目录下,创建了俩个yml文件,他们会被自动加载。这两个yml里则是配置要监控的机器信息,比如linux.yml中的内容如下 []里的就写被监控服务器的ip,web服务对应的端口都是9100,instance则是可以自定义写名称,方便在Grafana中查看,也是特别要注意缩进、空格等语法问题。 1.5 重启 Prometheus nohup ./prometheus --config.file=prometheus.yml & 安装Grafana Grafana是一款用Go语言开发的开源数据可视化工具,可以做数据监控和数据统计,带有告警功能。 此处我们要在监控平台所在服务器 1.1.1.1 (假设的IP)上操作 1.1下载 Grafana wget https://dl.grafana.com/oss/release/grafana-8.0.1.linux-amd64.tar.gz 1.2 解压 tar xvzf grafana-8.0.1.linux-amd64.tar.gz 1.3 启动 Grafana nohup ./bin/grafana-server web & 通过浏览器输入:http://your_ip:3000,进入Grafna的监控平台。一开始会让你输入用户名和密码,默认用户名和密码都是 admin。随后,你需要重新设置一个密码。登陆之后,会进入到 Grafna 的欢迎页面。 配置 Grafna 安装完成之后还是一个空壳,你的给他配置才能收集到服务器的数据信息 1.1 配置数据源(Data sources) 点击 Add data source 选择数据源为 Prometheus,点击 Select 在 “Settings” Tab下,填写 Name 和 URL URL 为 Prometheus 的服务地址。 填写完毕后,点击 Save & test 随后,再点击上边的 “Dashboards” Tab 把 Prometheus Stats、Prometheus 2.0 Stats、Grafana metrics 都 import 进来。 这样,数据源就已经配置好了。 1.2 配置 Dashboard 模版 在红框里填入8919,这是一个中文的 Dashboard 模版的ID,输入后,点击 Load。 如果想用别的 Dashboard,也可以上官网去自由选择别的展示面板,链接:https://grafana.com/dashboards 输入自定义的 Dashboad 名称,选择 VictoriaMetrics 下拉框为:Prometheus,然后点击 Import,这样就完成了 Dashboard 的配置。 1.3 炫酷的监控看板 至此就完成了linux服务的监测,下次我们来看看如何监控mysql服务。记得右下角点个赞哦~

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册