Armeria - 基于 Armeria 框架构建 gRPC服务

文章目录

  • 基础设施
    • 依赖插件配置
    • 编写 proto 文件
    • 编译 proto 文件
    • Armeria 集成 gRPC,启动服务
  • 开发
    • 基础设施
    • 创建操作
    • 读取操作
    • 修改操作
    • 删除操作

基础设施


依赖插件配置

Note:JDK 需要 11 及以上,Protobuf3.

import com.google.protobuf.gradle.idplugins {applicationkotlin("jvm") version "1.9.24"id("com.google.protobuf") version "0.9.3"
}group = "org.cyk"
version = "1.0-SNAPSHOT"repositories {mavenLocal()mavenCentral()
}protobuf {protoc {artifact = "com.google.protobuf:protoc:3.25.1"}plugins {id("grpc") {artifact = "io.grpc:protoc-gen-grpc-java:1.64.0"}}generateProtoTasks {all().forEach { task ->task.plugins {id("grpc")}}}
}dependencies {implementation ("com.linecorp.armeria:armeria:1.30.1")implementation ("com.linecorp.armeria:armeria-grpc:1.30.1")compileOnly("javax.annotation:javax.annotation-api:1.3.2")runtimeOnly ("ch.qos.logback:logback-classic:1.4.14")testImplementation ("org.junit.jupiter:junit-jupiter:5.10.3")testImplementation ("com.linecorp.armeria:armeria-junit5:1.30.1")testImplementation(kotlin("test"))
}tasks.test {useJUnitPlatform()
}kotlin {jvmToolchain(17)
}tasks.withType<JavaCompile> {sourceCompatibility = "17"targetCompatibility = "17"options.encoding = "UTF-8"options.isDebug = trueoptions.compilerArgs.add("-parameters")
}application {mainClass.set("MainKt")
}

编写 proto 文件

{project_root}/src/main/proto 下创建 blog.proto 文件,如下:

syntax = "proto3";package org.cyk.armeria.grpc.blog;
option java_package = "org.cyk.armeria.grpc.blog";
option java_multiple_files = true;import "google/protobuf/empty.proto";service BlogService {rpc CreateBlog (CreateBlogReq) returns (CreateBlogResp) {}rpc QueryBlogById (QueryBlogByIdReq) returns (QueryBlogByIdResp) {}rpc QueryBlogByIds (QueryBlogByIdsReq) returns (QueryBlogByIdsResp) {}rpc UpdateBlogById (UpdateBlogByIdReq) returns (UpdateBlogResp) {}rpc DeleteBlogById (DeleteBlogByIdReq) returns (google.protobuf.Empty) {}
}message CreateBlogReq {string title = 1;string content = 2;
}
message CreateBlogResp {Blog blog = 1;optional string errorMsg = 2;
}message QueryBlogByIdReq {    // For retrieving a single postint32 id = 1;
}
message QueryBlogByIdResp {optional Blog blog = 1;
}message QueryBlogByIdsReq {  // For retrieving multiple postsrepeated int32 ids = 1;
}
message QueryBlogByIdsResp {repeated Blog blogs = 1;
}message UpdateBlogByIdReq {int32 id = 1;string title = 2;string content = 3;
}
message UpdateBlogResp {Blog blog = 1;optional string errorMsg = 2;
}message DeleteBlogByIdReq {int32 id = 1;
}message Blog {int32 id = 1;string title = 2;string content = 3;int64 createdAt = 4;int64 modifiedAt = 5;
}

编译 proto 文件

Note:protobuf-gradle-plugin -> https://github.com/google/protobuf-gradle-plugin

gradlew 跳过测试并构建,protobuf-gradle-plugin 插件也会随之编译 proto 文件,如下命令:

gradlew build -x test

之后就可以在如下位置看到编译得到的文件:

  • { project_root }/build/generated/source/proto/main/grpc
  • { project_root }/build/generated/source/proto/main/java

Armeria 集成 gRPC,启动服务

1)创建 BlogServiceGrpcFacade 类,继承 BlogServiceImplBase,表示将来需要远程调用的对象,如下:

import example.armeria.blog.grpc.BlogServiceGrpc.BlogServiceImplBaseclass BlogServiceGrpcFacade: BlogServiceGrpc.BlogServiceImplBase()

2)配置 Armeria-gRPC 服务(这里没有使用 SpringBoot,默认构建 Bean)

import org.slf4j.LoggerFactory
import com.linecorp.armeria.server.Server
import com.linecorp.armeria.server.grpc.GrpcService
import service.BlogServiceGrpcFacadeobject ArmeriaGrpcBean {fun newServer(port: Int): Server {return Server.builder().http(port) // 1.配置端口号.service(GrpcService.builder().addService(BlogServiceGrpcFacade()) // 2.添加服务示例.build()).build()}
}fun main(args: Array<String>) {val log = LoggerFactory.getLogger("MainLogger")val server = ArmeriaGrpcBean.newServer(9000)server.closeOnJvmShutdown().thenRun {log.info("Server is closed ...")}server.start().join()
}

如果看到如下日志,表明服务正在运行:

22:27:51.746 [armeria-boss-http-*:9000] INFO com.linecorp.armeria.server.Server -- Serving HTTP at /[0:0:0:0:0:0:0:0]:9000 - http://127.0.0.1:9000/

开发

基础设施

1)客户端

    companion object {private lateinit var stub: BlogServiceBlockingStubprivate lateinit var server: Server@JvmStatic@BeforeAllfun beforeAll() {server = ArmeriaGrpcBean.newServer(9000)server.start()//这里启动不是异步的,所以不用 Thread.sleep 等待stub = GrpcClients.newClient("http://127.0.0.1:9000/",BlogServiceBlockingStub::class.java,)}}

2)服务端
这里为了专注 Armeria-gRPC 的处理,使用 map 来替代数据库

class BlogServiceGrpcFacade: BlogServiceGrpc.BlogServiceImplBase() {// ID 生成器private val idGenerator = AtomicInteger()// 数据库private val blogRepo = ConcurrentHashMap<Int, Blog>()}

创建操作

1)服务端

    override fun createBlog(request: CreateBlogReq,responseObserver: StreamObserver<CreateBlogResp>) {val id = idGenerator.getAndIncrement()val now = Instant.now()val blog = Blog.newBuilder().setId(id).setTitle(request.title).setContent(request.content).setModifiedAt(now.toEpochMilli()).setCreatedAt(now.toEpochMilli()).build()blogRepo[id] = blogval resp = CreateBlogResp.newBuilder().setBlog(blog).build()responseObserver.onNext(resp)responseObserver.onCompleted()}

2)客户端

    @Testfun createBlogTest() {val req = CreateBlogReq.newBuilder().setTitle("我的博客1").setContent("今天天气真不错~").build()println("================= req send ... =================")val resp = stub.createBlog(req)println(resp.blog.title)println(resp.blog.content)println("================= resp received ... =================")}

3)效果如下:

================= req send ... =================
22:14:03.812 [Test worker] INFO com.linecorp.armeria.internal.common.JavaVersionSpecific -- Using the APIs optimized for: Java 12+
22:14:03.923 [armeria-common-worker-nio-3-3] DEBUG com.linecorp.armeria.server.HttpServerHandler -- [id: 0xd94afbdc, L:/127.0.0.1:9000 - R:/127.0.0.1:55237] HTTP/2 settings: {ENABLE_PUSH=0, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192}
22:14:03.929 [armeria-common-worker-nio-3-2] DEBUG com.linecorp.armeria.client.Http2ResponseDecoder -- [id: 0xfd4c7207, L:/127.0.0.1:55237 - R:/127.0.0.1:9000] HTTP/2 settings: {MAX_CONCURRENT_STREAMS=2147483647, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192}
我的博客1
今天天气真不错~
================= resp received ... =================

读取操作

单个读取:
1)服务端

    override fun queryBlogById(request: QueryBlogByIdReq,responseObserver: StreamObserver<QueryBlogByIdResp>) {val resp = QueryBlogByIdResp.newBuilder()blogRepo[request.id]?.let {//这里的 it 不能为 null (proto 编译出的文件,只要 set,就不能为 null,除非你不 set)resp.setBlog(it)}//        如果不习惯, 可以对可能为空的字段滞后处理
//        val blog = blogRepo[request.id]
//        val resp = QueryBlogByIdResp.newBuilder().apply {
//            blog?.let { setBlog(it) }
//        }responseObserver.onNext(resp.build())responseObserver.onCompleted()}

2)客户端

    @Test@Order(1)fun createBlogTest() {//...}@Test@Order(2)fun queryBlogByIdTest() {val req = QueryBlogByIdReq.newBuilder().setId(0).build()println("================= req send ... =================")val resp = stub.queryBlogById(req)if (resp.hasBlog()) {println(resp.blog.title)println(resp.blog.content)}println("================= req received ... =================")}

3)效果如下:

================= req send ... =================
我的博客1
今天天气真不错~
================= req received ... =================

多个读取
1)服务端

    override fun queryBlogByIds(request: QueryBlogByIdsReq,responseObserver: StreamObserver<QueryBlogByIdsResp>,) {val blogs = blogRepo.filter {return@filter request.idsList.contains(it.key)}.map { it.value }val resp = QueryBlogByIdsResp.newBuilder().addAllBlogs(blogs).build()responseObserver.onNext(resp)responseObserver.onCompleted()}

2)客户端

    @Testfun queryBlogByIdsTest() {// init startval q1 = CreateBlogReq.newBuilder().setTitle("blog 1").setContent("balabala").build()stub.createBlog(q1)val q2 = CreateBlogReq.newBuilder().setTitle("blog 2").setContent("balabala").build()stub.createBlog(q2)val q3 = CreateBlogReq.newBuilder().setTitle("blog 3").setContent("balabala").build()stub.createBlog(q3)// init endval req = QueryBlogByIdsReq.newBuilder().addAllIds(listOf(0,1,2)).build()println("================= req send ... =================")val resp = stub.queryBlogByIds(req)resp.blogsList.forEach {println(it.title)}println("================= req received ... =================")}

3)效果如下:

================= req send ... =================
blog 1
blog 2
blog 3
================= req received ... =================

修改操作

1)服务端

    override fun updateBlogById(request: UpdateBlogByIdReq,responseObserver: StreamObserver<UpdateBlogResp>) {//这里的校验一般不再这一层做(还会有 Handler 读写分离类)val (errorMsg, beforeBlog) = checkAndGetPair(request)if (errorMsg != null) {responseObserver.onNext(UpdateBlogResp.newBuilder().setErrorMsg(errorMsg).build())responseObserver.onCompleted()return}val afterBlog = Blog.newBuilder().apply {id = beforeBlog!!.idtitle = request.titlecontent = request.content}.build()blogRepo[afterBlog.id] = afterBlogval resp = UpdateBlogResp.newBuilder().setBlog(afterBlog).build()responseObserver.onNext(resp)responseObserver.onCompleted()}private fun checkAndGetPair(req: UpdateBlogByIdReq): Pair<String?, Blog?> {val blog = blogRepo[req.id]?: return "文章不存在" to null// 如果还需要其他校验// ...return null to blog}

2)客户端

    @Testfun updateBlogTest() {// init startval q1 = CreateBlogReq.newBuilder().setTitle("blog 1").setContent("balabala").build()val blogBefore = stub.createBlog(q1)// init endprintln("update before =========================>")println("title: " + blogBefore.blog.title)println("update after =========================>")val updateReq = UpdateBlogByIdReq.newBuilder().setId(0).setTitle(q1.title + " update...").setContent(q1.content + " update...").build()val blogAfter = stub.updateBlogById(updateReq)println("title: " + blogAfter.blog.title)}

3)效果如下:

update before =========================>
title: blog 1
update after =========================>
title: blog 1 update...

删除操作

1)服务端

    override fun deleteBlogById(request: DeleteBlogByIdReq,responseObserver: StreamObserver<Empty>) {blogRepo.remove(request.id)responseObserver.onNext(Empty.getDefaultInstance())responseObserver.onCompleted()}

2)客户端

    @Testfun deleteByIdTest() {val cq = CreateBlogReq.newBuilder().setTitle("blog").setContent("balabala ...").build()stub.createBlog(cq)val qq = QueryBlogByIdReq.newBuilder().setId(0).build()stub.queryBlogById(qq).also {assertTrue { it.hasBlog() }}val dq = DeleteBlogByIdReq.newBuilder().setId(0).build()stub.deleteBlogById(dq)stub.queryBlogById(qq).also {assertTrue { !it.hasBlog() }}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/55300.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu 安装 Docker Compose

安装Docker Compose # 删除现有的 docker-compose&#xff08;如果存在&#xff09; sudo rm -f /usr/local/bin/docker-compose ​ # 下载最新的 docker-compose 二进制文件 sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-…

C++平台跳跃游戏

目录 开头程序Game.cpp源文件Player.h头文件Player.cpp源文件 程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 Game.cpp源文件 #include <iostream> #include "Player.h" using namespace std; void printma…

scrapy爬取汽车、车评数据【上】

这个爬虫我想分三期来写&#xff1a; ✅ 第一期写如何爬取汽车的车型信息&#xff1b; ✅ 第二期写如何爬取汽车的车评&#xff1b; ✅ 第三期写如何对车评嵌入情感分析结果&#xff0c;以及用简单的方法把数据插入mysql中&#xff1b; 技术基于scrapy框架、BERT语言模型、mysq…

433按键单片机解码

近段时间做项目要用到单片机接收433MHz按键发过来的码值&#xff0c;涉及短按、连按、长按&#xff0c;由于之前没有做过这方面一开始有点蒙&#xff0c;找遍网上都没有案例&#xff0c;现在项目完成了整理自己的一些心得和大家分享分享&#xff01;&#xff01;&#xff01;直…

JQuery基本介绍和使用方法

JQuery基本介绍和使用方法 W3C 标准给我们提供了⼀系列的函数, 让我们可以操作: ⽹⻚内容⽹⻚结构⽹⻚样式 但是原⽣的JavaScript提供的API操作DOM元素时, 代码⽐较繁琐, 冗⻓. 我们可以使⽤JQuery来操作⻚⾯对象. jQuery是⼀个快速、简洁且功能丰富的JavaScript框架, 于20…

【Spring】Spring Boot项目创建和目录介绍

文章目录 1 Spring Boot 介绍2 Spring Boot 项目创建注意事项 3. 项目代码和目录介绍pom 文件父工程目录介绍 1 Spring Boot 介绍 Spring 让 Java 程序更加快速、简单和安全&#xff0c;Spring 对于速度、简单性和生产力的关注使其成为世界上最流行的 Java 框架 Spring 官方提…

用Python+flask+mysql等开发的Excel数据资产落地工具

话不多说 1)Excel文件上传,列表预览 2)选中要导入结构及数据的Excel文件 约束说明: 2.1)Excel文件的第一行约定为表头名称 2.2)系统自动识别字段列名及数据类型,目前不支持合并表头 3)Excel建表导入数据成功后,可在表源列表中预览查看 4)对数据表源可进行透视图设计管理,可对…

滚雪球学Oracle[5.2讲]:数据库备份与恢复基础

全文目录&#xff1a; 前言一、备份策略的设计与实施1.1 备份的必要性1.2 备份的类型1.3 备份策略的设计示例&#xff1a;备份计划 二、增量备份与差异备份的配置2.1 增量备份的配置示例&#xff1a;配置增量备份 2.2 差异备份的配置示例&#xff1a;配置差异备份 三、使用RMAN…

叉车防撞系统方案,引领安全作业新时代

在现代工业的舞台上&#xff0c;叉车如同忙碌的“搬运工”&#xff0c;在仓储和制造环境中发挥着不可或缺的作用。然而&#xff0c;随着叉车使用频率的不断攀升&#xff0c;安全事故也如影随形&#xff0c;给企业带来经济损失的同时&#xff0c;更严重威胁着操作人员的生命安全…

15 数组——15. 三数之和 ★★

15. 三数之和 给你一个整数数组nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j、i != k且j != k,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。注意:答案中不可以包含重复的三元组。 示例 1: 输入:nums = [-1,0,1,2…

深入探讨Windows 11专业版与Windows 11专业工作站版的差异

前言 深入探讨Windows 11专业版与Windows 11专业工作站版的差异&#xff0c;可以更全面地理解这两款操作系统版本面向的不同用户群体、硬件支持、性能特点以及应用场景&#xff0c;从而为专业用户和企业选择最合适的平台提供依据。 硬件支持与扩展能力 Windows 11专业版&…

python select interpreter vscode 配置

vscode 没有 ctrl shift p&#xff0c;输入 python select interpreter 发现结果为空&#xff0c; 解决方法&#xff1a; 重新安装python插件。

Pytorch基本知识

model.state_dict()、model.parameters()和model.named_parameters()的区别 parameters()只包含模块的参数,即weight和bias(包括BN的)。 named_parameters()返回包含模块名和模块的参数的列表,列表的每个元素均是包含layer name和layer param的元组。layer param就是param…

HTB:Unified[WriteUP]

目录 连接至HTB服务器并启动靶机 1.Which are the first four open ports? 2.What is the title of the software that is running running on port 8443? 3.What is the version of the software that is running? 4.What is the CVE for the identified vulnerabilit…

数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall

数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall 数据集-目标检测系列-豹子 猎豹 检测数据集 leopard 数据量&#xff1a;5k 想要进一步了解&#xff0c;请联系。 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#x…

Unity2017在安卓下获取GPS位置时闪退的解决办法

在Unity使用低功耗蓝牙通信&#xff08;BLE&#xff09;需要用到设备的位置信息。但是调用Input.location.Start()程序会闪退。 解决办法&#xff1a;调用原生安卓接口。 参见《Unity2021通过aar调用Android方法》编写一个aar插件gpsplugin&#xff0c;在插件中提供获取GPS位…

微软SCCM:企业级系统管理的核心工具

目录 摘要 1. 引言 2. SCCM的基本概念 2.1 什么是SCCM? 2.2 SCCM的历史 3. SCCM的架构 3.1 中心服务器 3.2 数据库 3.3 管理点(Management Point) 3.4 分发点(Distribution Point) 3.5 客户端代理 3.6 报告服务 4. SCCM的核心功能 4.1 软件部署与管理 4.2 操…

docker管理

拉取容器镜像 docker pull 镜像名:镜像版本查看镜像 docker images查看容器列表 # 查看正在运行的容器 docker ps # 查看全部的容器(包括停止的容器) docker ps -a进入容器 docker exec -it 容器id /bin/bash停止容器 docker stop 容器id运行容器 docker start 容器id删除…

回溯算法框架解决排列组合及子集问题

216. 组合总和 III39. 组合总和40. 组合总和 II46. 全排列47. 全排列 II77. 组合 78. 子集 90. 子集 II 以上是力扣设计相关问题的题目。排列组合还是子集问题无非就是从序列 nums 中以给定规则取若干元素&#xff0c;主要有以下几类&#xff1a; 元素无重不可复选&#xff0…

贝锐蒲公英工业物联方案:助力美的智慧楼宇全球布局

智慧楼宇正日益成为现代城市发展的基石&#xff0c;作为该领域的先锋&#xff0c;美的楼宇科技通过其创新的iBUILDING数字化平台和低碳技术&#xff0c;引领着智慧空间的可持续发展&#xff0c;并持续推动建筑及相关行业的数字化转型。 美的楼宇科技的解决方案融合了先进的楼宇…