Skip to content
Reason94 edited this page Oct 9, 2022 · 13 revisions

写在前面

在百度内部,常用语言(C++,Java,Go,PHP)的RPC框架都有多种实现,RPC框架这种基础软件不可或缺,但都很难讲出业务or晋升的价值,所以大部分RPC框架都充满了个人色彩,很难坚持到发展成拥有一个团队or社区持续打磨成为一个特别完善的产品,随着主力作者的离职,很多RPC框架也就留在了代码库的角落。即便如此,在百度缺乏组织支持的情况,全凭戈神一己之力也诞生了brpc这样的作品,brpc框架在百度内部成为C++技术栈的事实性标准,实在令人敬佩。

回到Java技术栈,我们在2010年就已经开始自研RPC框架,彼时业界也并没有一款特别能打的选择,但随着时间流逝,Dubbo发展迅速也成为了Java技术栈的首选RPC框架,但遗憾的是时至今日,可能由于某种竞争关系,Dubbo仍然不支持brpc协议,brpc也不支持dubbo协议。因此在百度内部为实现Java系统和C++系统的通讯,于2017年发起了brpc-java项目对标Dubbo,希望成为brpc生态下面的Java框架首选,但正如戈神所说,RPC框架如果作为产品其实不应该只侧重性能而应该更侧重效率,真正帮助用户屏蔽底层系统的各种奇妙问题,可以低成本的正确使用。很遗憾主打高性能的brpc-java没能发展成Dubbo这样的项目,受限于资源局限,随着主力作者的离职,brpc-java也注定只能消失在代码库的角落。

受到戈神的启发,在Java技术栈,SpringBoot&SpringCloud才是对业务发展最有效率的应用框架,我们不应该另立门户搞一套类似Dubbo的全栈框架(资源限制也不允许),而应该在Spring生态下寻求解决方案,因此在2019年又发起了一个有全新定位的RPC框架项目,代号为Starlight,Starlight定位是一个兼容SpringBoot&SpringCloud生态的点对点通信框架,Starlight提供单端口兼容Spring MVC http rest协议和brpc二进制协议的能力,使用Spring MVC框架的用户可以较为容易的迁移过来,同时也无需protobuf的编译过程(当然如果非常需要proto文件来编译也是可以用的)实现对brpc C++ Server&Client的通信能力,其他的服务注册发现、配置管理、负载均衡、熔断限流完全依赖Spring Cloud生态的各种Starter,也可以灵活借助Service Mesh的能力实现服务注册发现过程,无需特别的兼容适配。

在百度商业体系,Starlight已经完成对存量各种Java RPC框架的替换迁移,规模达到2w+容器,日pv达到200亿+。同时由于brpc已经捐赠给Apache基金会,早前的brpc-java无人维护并不适合加入brpc生态,Starlight在百度内部已经是brpc-java替代产品,为了避免商标问题,Starlight顺势开源,brpc-java开源仓库也更名为Starlight,之前的代码保存在brpc-java-v3分支,希望外部brpc-java用户有多一种选择。

Starlight 简介

Starlight 是一套面向云原生的微服务通信框架,兼容Spring生态,基于此可快速构建高效、稳定、可控、可观测的微服务应用,获得研发效率提升、业务稳定性增强等舒适体验。 核心特性如下:

  • 多种协议支持:Starlight单端口支持brpc、stargate、spring mvc rest(http)协议,提供超丰富的使用场景
  • 高性能远程通信:Starlight基于多路复用的NIO框架封装底层通性能力,提供高性能高并发网络通信能力
  • 易于使用:无需处理protobuf编译过程,通过原生Java接口和POJO对象加上类级别的注解,类似Java RMI和Spring MVC使用体验,即可实现brpc二进制协议的Server和Client;支持无损升级、异常实例摘除;规范化的日志可以秒级定位超时问题、序列化失败问题

快速开始

非Spring场景(例如Spark,或者更编程化的控制RPC通信)

不使用proto编译

Java POJO类定义调用API。用于Java语言间调用,以及跨语言proto文件可手动翻译为Java接口和POJO对象。

  1. 添加maven依赖
<dependency>
    <groupId>com.baidu.cloud</groupId>
    <artifactId>starlight-all</artifactId>
    <version>${最新版本}</version>
</dependency>
  1. 为服务端和消费端创建公共接口

跨语言场景按照proto文件定义翻译为Java接口与POJO定义

src/main/java/com/baidu/cloud/demo/api/UserService.java

public interface UserService {
    User getUser(Long userId);
}
  1. 服务端编写业务逻辑并启动

编写业务逻辑

src/main/java/com/baidu/cloud/demo/provider/service/UserServiceImpl.java

public class UserServiceImpl implements UserService {
    @Override
    public User getUser(Long userId) {
        User user = new User();
        user.setUserId(userId);
        user.setUserName("User1");
        user.setBalance(1000.21d);
        user.setGender(Gender.MALE);
        List<String> tags = new LinkedList<>();
        tags.add("fgh");
        tags.add("123123");
        user.setTags(tags);
        List<ExtInfo> extInfos = new LinkedList<>();
        ExtInfo extInfo = new ExtInfo("hobby", "learn");
        extInfos.add(extInfo);
        user.setExtInfos(extInfos);
        user.setMap(Collections.singletonMap("key", new Address("Beijing")));
        return user;
    }
}

启动server

src/main/java/com/baidu/cloud/demo/provider/DemoProviderApp.java

public class DemoProviderApp {
    public static void main(String[] args) {
        // 初始化server
        TransportConfig transportConfig = new TransportConfig();
        StarlightServer starlightServer = new DefaultStarlightServer("localhost", 8005, transportConfig);
        starlightServer.init();

        // 暴露接口信息
        ServiceConfig serviceConfig = new ServiceConfig();
        starlightServer.export(UserService.class, new UserServiceImpl(), serviceConfig);

        // 开启服务
        starlightServer.serve();

        synchronized (DemoProviderApp.class) {
            try {
                DemoProviderApp.class.wait();
            } catch (Throwable e) {
            }
        }
    }
}
  1. 客户端创建并发起调用

src/main/java/com/baidu/cloud/demo/consumer/DemoConsumerApp.java

public class DemoConsumerApp {

    public static void main(String[] args) {
        // 创建Client
        TransportConfig config = new TransportConfig(); // 传输配置
        StarlightClient starlightClient = new SingleStarlightClient("localhost", 8005, config);
        starlightClient.init();

        // 服务配置
        ServiceConfig clientConfig = new ServiceConfig(); // 服务配置
        clientConfig.setProtocol("brpc");
        // clientConfig.setServiceId("demo.EchoService"); // 跨语言时指定服务端定义的serviceName

        // 生成代理
        JDKProxyFactory proxyFactory = new JDKProxyFactory();
        UserService userService = proxyFactory.getProxy(UserService.class, clientConfig, starlightClient);

        // 发起调用
        User user = userService.getUser(1L);
        System.out.println(user.toString());

        // 销毁client
        starlightClient.destroy();

        System.exit(0);
    }
}

使用proto编译

编译proto文件为调用API。特用于客户端跨语言调用时,proto文件复杂无法手动翻译为POJO类。

  1. 添加maven依赖

按照如下添加并配置protoc-jar-maven-plugin,执行maven compile将proto文件编译为Java类。

<dependency>
    <groupId>com.baidu.cloud</groupId>
    <artifactId>starlight-all</artifactId>
    <version>${最新版本}</version>
</dependency>

    <build>
        <plugins>
            <!--编译proto文件为Java对象-->
            <plugin>
                <groupId>com.github.os72</groupId>
                <artifactId>protoc-jar-maven-plugin</artifactId>
                <version>3.6.0.1</version>
                <executions>
                    <execution>
                        <id>generate-sources</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <!--仅支持pb2.5-->
                            <protocVersion>2.5.0</protocVersion>
                            <addSources>none</addSources>
                            <includeStdTypes>true</includeStdTypes>
                            <!--proto文件编译生成的Java类存放位置-->
                            <outputDirectory>src/main/java</outputDirectory>
                            <inputDirectories>
                                <!--proto文件存放位置-->
                                <include>src/main/proto</include>
                            </inputDirectories>
                        </configuration>
                    </execution>
                    <execution>
                        <id>generate-test-sources</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <protocVersion>2.5.0</protocVersion>
                            <includeStdTypes>true</includeStdTypes>
                            <addSources>none</addSources>
                            <outputDirectory>src/test/java</outputDirectory>
                            <inputDirectories>
                                <include>src/test/proto</include>
                            </inputDirectories>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
  1. 客户端创建公共接口

客户端依照proto文件手写Java接口作为与服务端交互的接口,methodName需与proto文件中一致,接口名不做要求,参数与返回值为上步生成的Java类。

public interface UserProtoService {
    // UserPb.UserProto为proto编译插件生成的类
    // Echo与proto文件定义的方法名一致
    UserPb.UserProto Echo(UserPb.UserProto user);
}
  1. 客户端创建并发起调用
public class BrpcProtoConsumer {

    public static void main(String[] args) {

        // 创建Client
        TransportConfig config = new TransportConfig(); // 传输配置
        StarlightClient starlightClient = new SingleStarlightClient("localhost", 8005, config);
        starlightClient.init();

        // 服务配置
        ServiceConfig clientConfig = new ServiceConfig(); // 服务配置
        clientConfig.setProtocol("brpc");
        clientConfig.setServiceId("demo.UserService"); // 跨语言时指定服务端定义的serviceName

        // 生成代理
        JDKProxyFactory proxyFactory = new JDKProxyFactory();
        UserProtoService userService = proxyFactory.getProxy(UserProtoService.class, clientConfig, starlightClient);
     
        // 发起调用
        UserPb.ExtInfoProto extInfoProto = UserPb.ExtInfoProto.newBuilder()
                .setKey("brpckey")
                .setValue("brpcvalue")
                .build();

        UserPb.UserProto user = UserPb.UserProto.newBuilder()
                .setUserId(12L)
                .setUserName("Brpc User")
                .setAge(12)
                .setAlive(true)
                .setBalance(123.12d)
                .setSex(UserPb.SexProto.FEMALE)
                .setSalary(121.4f)
                .setAddress(UserPb.AddressProto.newBuilder().setAddress("Beijing").build())
                .addAllTags(Arrays.asList("BrpcTag1", "BrpcTags2"))
                .addExtInfos(extInfoProto)
                .build();

        try {
            UserPb.UserProto result = userService.Echo(user);
            System.out.println("User result is " + result);
        } catch (Exception e) {
            System.out.println("Exception occur");
            e.printStackTrace();
        }

        clusterClient.destroy();
        System.exit(0);
    }
}

SpringBoot场景(类似Spring MVC)

SpringCloud场景(完整的微服务应用)

源码构建

微信交流群

添加管理员olivaw2077帮忙加群,备注starlight

Introdution

Getting Started

Design Documment

Communication

Information

Clone this wiki locally