# SPeed-RPC 手写基于注解声明式服务接口 RPC 框架 **Repository Path**: tlgen_1/spring-boot-rpc ## Basic Information - **Project Name**: SPeed-RPC 手写基于注解声明式服务接口 RPC 框架 - **Description**: 1.基于注解 @Fetch 面向接口设计, 声明式服务调用 2.支持负载均衡策略,默认随机策略 3.统一返回响应体,由于 RPC 调用返回类型不可预测,定义统一响应报文 4.支持从 Nacos 注册中心中拉取服务列表 5.支持底层 OkHttp3 ,设置请求超时时间、读写超时时间配置 6.服务熔断降级 forback 返回托底数据响应,正在研发... - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-12-05 - **Last Updated**: 2023-12-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 手写 RPC 框架 #### 1.SPeed-RPC 框架简介 ```ruby 1.基于注解 @Fetch 面向接口设计, 声明式服务调用 2.支持负载均衡策略,默认随机策略 3.统一返回响应体,由于 RPC 调用返回类型不可预测,定义统一响应报文 4.支持从 Nacos 注册中心中拉取服务列表 5.支持底层 OkHttp3 ,设置请求超时时间、读写超时时间配置 6.服务熔断降级 forback 返回托底数据响应,正在研发... ``` ## 技术实现分享: #### 1.Spring 中接口如何作为 bean 进行注册? ```ruby 首先,在 Spring 中接口是无法做为 bean 进行注册的,至于怎么做, 有一招偷天换日,使用 JDK 动态代理,把接口通过动态代理类去做为实际的 Bean, 即代理对象,即实现层,这里可以去参考 MyBatis Plus 框架的底层设计技巧, 这里基于手动封装的 @Fetch 注解以及反射 结合 Spring 框架 Bean 生命周期扩展接口进行 Bean 的注册, 当然还会涉及到一招叫鬼斧神工,Spring 的 FactoryBean。 ``` #### 2.请求框架是使用的什么技术方案? ```ruby 使用的 OkHttp3,该框架基于池化技术进行了优化。 ``` #### 3.如何实现负载均衡策略? ```ruby 手写负载均衡实现算法,Hash 算法、随机算法、轮询算法、加权随机、加权轮询。 ``` #### 4.如何从主流的注册中心组件中拉取服务列表? ```ruby 有多种思路,比如 集成 Nacos 的客户端,根据服务名手动拉取; 或者通过反射加载 Eureka 扩展接口类,调用相关实现, 如果该服务本身集成 Eureka,那么可以成功拉取; 或者基于定时配合事件监听去拉取配置都可以。 ``` #### 5.是否支持 ribbon 的负载均衡策略,配置是否生效? ```ruby 支持部分,且生效。基于环境配置判断匹配去选择对应负载均衡策略。 ``` ## 快速上手(集成案例分享) ### 一、准备一个服务提供者 #### 1.服务提供者项目 ```ruby spring-boot-mytest 这里集成了 nacos ,当然也可以不用集成 nacos,可以手动指定服务信息, 这里只是以微服务的方式举例。 ``` ![1](doc/1.png) #### 2. application.yml ```ruby 微服务项目,可以参考这里集成 Nacos 基础的配置。 ``` ```ruby spring: application: name: base-info # nacos相关配置 nacos: discovery: server-addr: 127.0.0.1:8848 #是否将本应用注册到nacos服务列表,默认是false auto-register: true #本服务所属的命名空间的ID,默认是空,也就是public namespace: public register: #本服务是否接受外部的请求,默认true enabled: true #本服务要注册到命名空间下的哪个组,默认DEFAULT_GROUP group-name: DEFAULT_GROUP #服务的名称,会展示在nacos服务列表,要求唯一,可以不写,默认是spring.application.name的值 service-name: ``` #### 3.注册到 nacos ![3](doc/3.png) ### 二、搭建一个消费者服务 #### 1.服务消费者项目 ```ruby spring-boot-mylocal ``` ![2](doc/2.png) #### 2.集成 rpc 框架依赖 ```ruby com.gitee_1 speed-rpc 1.0.0 ``` #### 3.声明服务调用接口 ```ruby SysUserInterface.java 接口 @Fetch 注解介绍 - name:提供者服务名称,必输 - pattern:请求路径匹配, 如 pattern = "/order" 可以匹配到 /order-info,可选 - url:手动指定的单个服务请求地址,设置该属性则不走负载均衡策略,可选 ``` ```ruby package com.example.interfaces; import com.example.model.SysUser; import com.tlgen.rpc.annotation.Fetch; import com.tlgen.rpc.comon.R; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @Fetch(name = "base-info") public interface SysUserInterface { @GetMapping("/selectByUsername") R selectByUsername(String username); @GetMapping("/selectByCondition/{mobile}/{email}") R selectByCondition(@PathVariable("mobile") String mobile, @PathVariable("email") String email); @PostMapping("/sendMsg") R sendMsg(@RequestParam("username") String username, @RequestParam("mobile") String mobile); @PostMapping("/saveUser") R saveUser(@RequestBody SysUser user); @PutMapping("/updateUser") R updateUser(@RequestBody SysUser user); @DeleteMapping("/deleteUser") R deleteUser(@RequestBody SysUser user); @DeleteMapping("/deleteUserPath/{mobile}/{email}") R deleteUserPath(@PathVariable("mobile") String mobile, @PathVariable("email") String email); @PostMapping("/updateRole") R updateRole(@RequestBody Map map); @PostMapping("/send") R send(@RequestBody String jsonString); @PutMapping("/saveBatch") R saveBatch(@RequestBody List userList); } ``` #### 4.写个接口测试调用 ```ruby SysUserController.java 控制类 以 Bean 的方式把 SysUserInterface 接口进行注入调用 ``` ```ruby package com.example.controller; import com.example.interfaces.SysUserInterface; import com.example.model.SysUser; import com.tlgen.rpc.comon.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/user") public class SysUserController { @Autowired private SysUserInterface userInterface; @GetMapping("/testApi") public String testApi() { // 测试 get 条件查询 ? 的方式 R ret1 = userInterface.selectByUsername("小e"); // 测试 get 条件查询 @PathVariable 方式 R ret2 = userInterface.selectByCondition("13617786374", "abc@163.com"); // 测试 post 传多个参数, @RequestParam 修饰参数 R ret3 = userInterface.sendMsg("13617786374", "abc@163.com"); // 测试 post 传对象类型 SysUser, @RequestBody 修饰参数 SysUser user = new SysUser(); user.setId("1"); user.setUsername("小e"); user.setMobile("13617786374"); user.setEmail("abc@163.com"); user.setGender(1); R ret4 = userInterface.saveUser(user); // 测试 put 传对象类型 SysUser, @RequestBody 修饰参数 R ret5 = userInterface.updateUser(user); // 测试 delete 传对象类型 SysUser, @RequestBody 修饰参数 R ret6 = userInterface.deleteUser(user); // 测试 delete 使用 @PathVariable 修饰参数的方式 R ret7 = userInterface.deleteUserPath("13617786374", "abc@163.com"); // 测试 post 使用 @RequestBody 修饰参数, 传一个 Map 的方式 Map map = new HashMap<>(); map.put("id", "1"); map.put("username", "小e"); map.put("email", "abc@163.com"); // 测试 post 使用 @RequestBody 修饰参数, 传一个 JSON 字符串的方式 R ret8 = userInterface.updateRole(map); String jsonString = "{\n" + " \"userInfo\":{\n" + " \"id\":\"1\",\n" + " \"username\":\"小e\",\n" + " \"mobile\":\"13617786374\",\n" + " \"email\":\"abc@163.com\"\n" + " }\n" + "}"; R ret9 = userInterface.send(jsonString); // 测试 put 使用 @RequestBody 修饰参数, 传一个 List 的方式 List userList = new ArrayList<>(); userList.add(user); R ret10 = userInterface.saveBatch(userList); System.out.println(ret1); System.out.println(ret2); System.out.println(ret3); System.out.println(ret4); System.out.println(ret5); System.out.println(ret6); System.out.println(ret7); System.out.println(ret8); System.out.println(ret9); System.out.println(ret10); return "SUCCESS"; } } ``` #### 5.测试获取输出结果 ```ruby R(code=200, msg=操作成功!, data=小e) R(code=200, msg=操作成功!, data=13617786374 ==> abc@163.com) R(code=200, msg=操作成功!, data=13617786374 ==> abc@163.com) R(code=200, msg=操作成功!, data={"id":"1","username":"小e","password":null,"email":"abc@163.com","gender":1}) R(code=200, msg=操作成功!, data={"id":"1","username":"小e","password":null,"email":"abc@163.com","gender":1}) R(code=200, msg=操作成功!, data=delete:{"email":"abc@163.com","gender":1,"id":"1","username":"小e"}) R(code=200, msg=操作成功!, data=delete:13617786374 ==> abc@163.com) R(code=200, msg=操作成功!, data=map:{id=1, email=abc@163.com, username=小e}) R(code=200, msg=操作成功!, data="{\n \"userInfo\":{\n \"id\":\"1\",\n \"username\":\"小e\",\n \"mobile\":\"13617786374\",\n \"email\":\"abc@163.com\"\n }\n}") R(code=200, msg=操作成功!, data=saveBatch:[{"email":"abc@163.com","gender":1,"id":"1","username":"小e"}]) ``` #### 6.可选配置负载均衡 ```ruby # 兼容 ribbon 的配置方式 base-info: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule ``` #### 7.可选请求相关配置 ```ruby okhttp: connectTimeout: 3 readTimeout: 3 writeTimeout: 3 ``` #### 8.指定转发到某个服务 ```ruby # 设置 url 属性 @Fetch(name = "my-test", url = "http://localhost:8081") public interface SysUserInterface { ... } ``` #### 9.指定请求匹配规则 ```ruby # 设置 pattern 属性 @Fetch(name = "my-test", pattern = "/test/**", url = "http://localhost:8081") ```