微服务框架模板
微服务框架模板
学习核心
- 微服务脚手架模板
- 基于微服务脚手架模板快速开发demo项目
学习资料
微服务脚手架模板说明
1.模板核心说明
环境配置
- JDK 1.8
- 注册中心 nacos (nacos-client依赖),用于服务治理和服务发现
- MySQL
- Redis
- Kafka 高性能、分布式的消息队列
脚手架模块说明
- svr(父工程)
- svr-common(公共依赖定义:例如公共实体、工具类等)
- svr-api (公共接口定义,此处只定义微服务接口,具体的实现由相应的业务模块实现)
- svr-demo(微服务demo模板,快速开发微服务模块)
脚手架模板配置
svr 是一个父工程(做依赖聚合管理,是一个pom工程),其下包括多个子工程(可以理解为子模块),如果要新建一个子模块,则可基于svr-demo进行构建,copy一份svr-demo,将工程重命名为自己的项目名(例如svr-xtimer),调整pom中相关配置,构建svr与svr-xtimer的模块关系(在svr引入子模块svr-xtimer,在svr-xtimer中引入父依赖svr)
1. 复制`svr-demo`修改项目名为新项目名称(建议本地文件夹复制,不要在idea中复制)
2. 修改新项目`pom.xml`文件`artifactId`节点为新项目名称
3. 修改根项目`pom.xml`文件`modules`节点,加入新项目
4. 修改`package`包名,包括 import 的包名
5. 修改`启动类名称`
2.依赖中间件说明
当前服务依赖的中间件为:MySQL、Redis、Nacos、Kafka
MySQL:底层存储,用来存储持久化数据(例如案例中的Example表)
Redis:由于其基本都将数据维护在内存中进行操作,所以拥有数据操作快的特性,一般用来做缓存(例如查询数据时将结果临时存储在Redis,下次查询时直接从Redis拿到结果)。另外rediscover凭借其丰富的数据结构和性能,所以可以用来设计一些系统优化点,例如Xtimer定时微服务中,就将近期待执行的定时任务全部维护在不同的zset数据结构中,为任务处理的分治策略等设计的实现,提供了基础
Nacos:nacos是用来做服务治理和服务发现的,当定义好一个微服务接口之后,需要完成服务注册之后,后续才能被接口调用者使用。 调用者使用feign客户端,像调用本地方法一样调用远程的微服务接口。
Nacos则是作为服务注册中心
- 服务提供者(Provider)在启动时,将自己的服务信息注册到Nacos注册中心,包括服务名称、IP地址、端口号等
- 服务消费者(Consumer)在启动时,从Nacos注册中心获取服务提供者的服务信息,并将这些信息缓存到本地
Feign实现服务调用(服务消费者使用Feign客户端进行服务调用。Feign是一个声明式的Web服务客户端,使得编写HTTP客户端变得更容易)
- 在服务消费者的项目中,通过定义Feign接口并使用注解来配置调用的服务提供者的接口(例如案例中使用@FeignClient注解来指定服务提供者的服务名称,使用@GetMapping或@PostMapping等注解来指定调用的URL和HTTP方法)
- 当服务消费者调用Feign接口时,Feign会根据配置自动生成HTTP请求,并发送给对应的服务提供者服务提供者接收到请求后,处理请求并返回结果给服务消费者
Kafka:一种高性能、分布式的消息队列,一般常用的场景有
- 消息传递:Kafka可以用于在分布式系统中进行消息传递
- 生产者(producer)向Kafka的一个(或多个)topic发送消息,消费者(consumer)订阅这些topic并处理接收到的消息
- 日志收集:Kafka可以收集各种服务的日志,并将其统一汇总,然后实时地传输到大数据处理平台进行处理
- 数据集成:Kafka可以作为数据集成的一种方式,它可以连接各种数据源和数据目的地,使得数据可以在不同的系统之间流动
- 数据缓冲:在数据处理过程中,如果下游系统处理速度跟不上上游系统,Kafka可以起到缓冲作用,将上游产生的数据暂时存放在Kafka中,等待下游系统慢慢处理
- 降低系统组网复杂度:Kafka可以作为高速数据总线,各个子系统不再是相互协商接口,而是以类似插口插在插座上的方式连接在一起
- 降低编程复杂度:各个子系统通过Kafka进行通信,无需关注彼此的实现细节,降低了编程的复杂度
微服务脚手架使用
微服务脚手架中提供了基本功能开发接入需要的能力,此处梳理相关功能的接入思路和使用方法。
前置环境
- nacos:
sh startup.sh -m standalone
(本地启动模式为单机模式,如果以集群方式启动可能会报错,注意启动命令配置)- 版本确认:mac下nacos-2.1.1版本安装失败,暂时降低nacos版本到1.4.7进行测试验证,后续在windows、docker环境下确认配置
- 启动后查看启动日志信息
...../nacos/nacos-1.4.7/logs/start.out
- 访问:http://localhost:8848/nacos/(nacos、nacos)
- mysql
- redis
- kafka
1.如何对外提供http接口?
如果开发的服务需要提供web服务功能,需要对外提供http接口,参考下述步骤
【1】引入springboot 框架下提供web服务的相关依赖(版本由父工程统一管理)
<!--web服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
【2】在controller 中编写相关接口(参考DemoController)
/**
* web服务接口:http 接口
**/
@RestController
@RequestMapping("/demo")
@Slf4j
public class DemoController {
@GetMapping("/hello")
public ResponseEntity<String> helloworld() {
// 消息队列发送消息
return ResponseEntity.ok(
"hello world"
);
}
}
【3】启动DemoApplication,测试接口调用
- 访问URL:http://127.0.0.1:8081/demo/hello
方式1:浏览器或者接口测试工具访问URL
方式2:通过curl工具访问curl --location 'http://127.0.0.1:8081/demo/hello'
2.如何提供对外提供微服务接口,供其他服务调用?(nacos、OpenFegin)
微服务之间的调用,目前采用的是基于OpenFeign
什么是OpenFeign? (此处可以结合OpenFeign的使用,与dubbo(RPC框架)的应用进行对比)
OpenFeign是一种声明式服务调用的客户端,声明式调用=>可以做到使用 HTTP请求远程服务时能就像调用本地方法一样的体验,开发者完全感知不到这是远程方法。OpenFeign的应用,让Spring Cloud微服务调用像Dubbo一样Application Client直接通过接口方法调用Application Service,它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
如何开发使用?
- 接口定义(统一接口定义):将接口的定义统一放在
svr-api
模块 - 接口实现(生产者):
- 在提供微服务接口(例如
svr-demo
)的工程中引入svr-api
模块,然后创建类实现相应的接口,并通过相关配置将接口暴露出去提供给外部服务调用
- 在提供微服务接口(例如
- 接口调用(消费者):
- 在调用微服务接口(例如
svr-testConsumer
)的工程中引入svr-api
模块,然后像调用本地方法一样去调用生产者提供的方法即可
- 在调用微服务接口(例如
构建步骤
svr-api
:在一个微服务架构体系中,此处将提供给外部的微服务接口定义都在svr-api
中统一定义,生产者、消费者只需要引入svr-api
依赖、装载注册中心相关配置即可构建联结。如果将接口定在各个微服务自己的module中,对于调用者而言就需要加入多个依赖,且不好进行管理
参考项目现有架构说明,在svr-api
模块中新增一个对外提供服务的接口定义(此处仅定义接口,不涉及接口实现,接口实现则由各自的微服务模块实现处理)。此处选用的RPC框架是基于OpenFeign,因此此处将接口定义在feign
包下(结合业务模块设定灵活自定义),以svr-api
的DemoClient
接口定义为例,在svr-demo
模块中实现DemoClient
接口
svr-api:定义接口DemoClient
@FeignClient(name="svr-demo",url="http://localhost:8081") // 调用
public interface DemoClient {
/**
* demo 服务接口 定义
*/
@GetMapping(value = "/call")
public String call(@RequestParam("name") String name);
}
svr-demo:模拟生产者:基于feign提供微服务接口
引入svr-api
模块,此处对应提供接口实现
@RestController
@Slf4j
public class DemoFeignController implements DemoClient {
public String call(String name) {
return "我是服务提供者svr-demo,hello: " + name;
}
}
svr-testConsumer:模拟消费者:调用微服务接口
引入svr-api
模块,模拟调用接口方法
@RestController
@RequestMapping("/demo")
@Slf4j
public class ConsumerController {
@Resource
private DemoClient demoClient;
@GetMapping("/example")
public ResponseEntity<String> consumer() {
// 调用方法
String resultStr = demoClient.call("hello openFeign test");
log.info("Result:" + resultStr);
return ResponseEntity.ok(
"result:"+resultStr
);
}
}
启动svr-testConsumer
项目,然后访问接口(打断点测试是否正常进入接口方法)。http://127.0.0.1:8093/demo/example
可以看到,调用者通过接口的注解配置定位到对应服务提供方法提供的接口方法,然后进行调用,返回结果
{
"code": 0,
"message": null,
"datetime": "2024-08-16T14:13:24.93",
"data": "result:我是服务提供者svr-demo,hello: hello openFeign test"
}
3.如何操作MySQL数据库?(MySQL)
数据库操作核心步骤
- pom.xml 中引入相关依赖 (mysql连接依赖、ORM框架mybatis)
- 数据库连接配置:application.yml
- 基于MVC构建接口实现,测试
构建步骤
pom.xml 依赖构建
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
application.yml 数据库配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/svr-xxx?serverTimezone=GMT%2B8&characterEncoding=utf8&useSSL=false
username: root
password: 123456
MVC 构建
此处以操作example表为例,访问接口测试功能。(回归常规的CRUD操作,此处不做赘述,调整配置访问接口测试功能正常访问即可)
CREATE DATABASE /*!32312 IF NOT EXISTS*/`db_svr_demo` /*!40100 DEFAULT CHARACTER SET utf8 */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `db_svr_demo`;
# 创建数据表
DROP TABLE IF EXISTS `example`;
CREATE TABLE `example` (
`example_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ExampleID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`example_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Example名称',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态 1:待付款 2:待发货 3:待收货(已发货) 5:成功 6:失败',
PRIMARY KEY (`example_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='订单信息';
@RestController
@RequestMapping("/example")
@Slf4j
public class ExampleController {
@Autowired
private ExampleService exampleService;
@GetMapping("/testDB")
public ResponseEntity<String> testDB() {
Example example = new Example();
example.setExampleName("测试样例 AAA");
example.setStatus(1);
exampleService.save(example);
System.out.println(exampleService.getExampleById(example.getExampleId()));
example.setExampleName("测试样例 BBB");
exampleService.update(example);
System.out.println(exampleService.getExampleById(example.getExampleId()));
exampleService.deleteExampleById(example.getExampleId());
System.out.println(exampleService.getExampleById(example.getExampleId()));
return ResponseEntity.ok(
"hello world"
);
}
}
接口模拟测试:http://localhost:8081/example/testDB
4.如何操作Redis数据库?(Redis)
Redis操作核心步骤
- pom.xml 中引入相关依赖 (Redis相关依赖)
- 数据库连接配置:application.yml
- 测试Redis连接
构建步骤
pom.xml 依赖构建
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml 配置
spring:
redis:
host: localhost
port: 6379
password: 123456
database: 0
jedis:
pool:
max-active: 8 #最大连接数
Redis 操作测试
接口模拟测试:http://localhost:8081/example/testRedis (测试将Example实体存入Redis缓存,然后再从缓存中读取数据,响应给客户端)
@RestController
@RequestMapping("/example")
@Slf4j
public class ExampleController {
@Autowired
private ExampleService exampleService;
@GetMapping("/testRedis")
public ResponseEntity<String> testRedis() {
// 构建测试
Example example = new Example();
example.setExampleId(111L); // exampleId作为存储的key值不能为空
example.setExampleName("测试样例 AAA");
//redis
exampleService.cacheExampleToRedis(example);
Example findExample = exampleService.getExampleFromRedis(example.getExampleId().toString());
// redis lua
System.out.println(findExample);
return ResponseEntity.ok(JSON.toJSONString(findExample));
}
}
// output
{"exampleId":111,"exampleName":"测试样例 AAA"}
如果希望搭配lua脚本使用,则可参考:RedisController 中的 /redisLuaDemo
测试
5.如何使用消息队列?(Kafka)
消息队列操作核心步骤
- pom.xml 中引入相关依赖 (Kafka 相关依赖)
- kafka配置:application.yml
- 测试kafka生产消息和消费消息
构建步骤
引入依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
kafka 配置
spring:
kafka:
#以逗号分隔的主机:端口对列表,用于建立与Kafka的连接
bootstrap-servers: 127.0.0.1:9092
producer:
# 发生错误后,消息重发的次数
retries: 0
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算
batch-size: 16384
# 设置生产者内存缓冲区的大小
buffer-memory: 33554432
# 键的序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
value-serializer: org.apache.kafka.common.serialization.StringSerializer
acks: 1
consumer:
# 键的反序列化方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
测试kafka生产消息和消费消息
@RestController
@RequestMapping("/kafka")
@Slf4j
public class KafkaController {
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
@GetMapping("/sendMessage")
public ResponseEntity<String> sendMessage() {
// 消息队列发送消息
kafkaTemplate.send("hello-world","来自Kafka的测试消息【ABAB】");
return ResponseEntity.ok(
"发送消息成功"
);
}
}
- 访问接口:http://localhost:8081/kafka/sendMessage ,模拟生产消息
- 测试模拟消费消息:查看
kafka.consumer#HelloWorldConsumer
常见问题处理
1.项目启动失败
nacos 异常:com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING
考虑是nacos版本问题:需要对照springboot项目中spring-cloud、nacos的依赖版本是否兼容,以及nacos注册中心版本
如果nacos注册中心版本为1.4.7,则相应需要调整com.alibaba.nacos.nacos-client
的依赖版本(将原版本2.2.0降低到1.4.7)
如果要适配nacos-client
的2.2.0版本,则需要相应注意部署的nacos版本(mac下配置需注意nacos1.0与nacos2.0的版本差异和配置要求,相应进行调整)
目前存在问题mac下安装配置nacos-2.1.1版本启动错误,考虑可能是m1芯片和nacos版本不兼容的问题,只能将版本降到nacos1.4.7进行测试
后续在windows下启动确认,还需确认nacos-2.x版本的端口偏移量问题(如果是基于docker部署则需将相关端口暴露出去)
todo:windows 适配nacos-2.0x版本进行测试,此处暂时降低配置,可能会遇到各种各样的问题待确认