深圳幻海软件技术有限公司 欢迎您!

hyperf/go/springboot通过jsonrpc通信

2023-03-13

一、背景随着用户的增长和业务的增多,单节点服务已经满足不了需求,用hyperf对主业务进行了重构。hyperf是一个后现代的php框架,基于php+swoole,支持协程,解决了php让人诟病的性能问题和多线程支持不够的问题。官方也提供了各种组件,比如配置中心、定时任务、消息队列和微服务,对于日常业

一、背景

随着用户的增长和业务的增多,单节点服务已经满足不了需求,用hyperf对主业务进行了重构。

hyperf是一个后现代的php框架,基于php+swoole,支持协程,解决了php让人诟病的性能问题和多线程支持不够的问题。官方也提供了各种组件,比如 配置中心、定时任务、消息队列和微服务,对于日常业务需求,基本能做到开箱即用,有点php界的springcloud的意思。

用过hyperf的微服务后,眼前一亮。hyperf采用了新起一个Server,在Service层接收和返回数据,本地也可以复用这些Service,通信协议用的是jsonrpc2.0,既支持http协议,又支持tcp协议,用http协议调试时可以直接用postman,方便直观,服务安全方面,线上服务器只要对微服务端口进行隔离,不对外部服务器开放即可。

之前在与JAVA组进行业务对接用的是restful,现在hyperf又提供了一种新的思路,重新设计架构。

二、架构


如上图所示,在后台服务与服务之间,采用jsonrpc进行交互,根据业务分库分表,不同业务之前不直接访问数据库。上层独立出子项目,每个子项目包含一个前端和一个业务中台,业务中台采用php,不直接访问数据库,从根据不同的业务从不同的业务后台取数据,进行数据组装。

三、代码

下面以加法运算为例,展示一下springboot、hyperf、go之间相互请求。

1、PHP

  • 服务端代码(以http协议为例)
<?php
declare(strict_types=1);

namespace App\JsonRpc;

use Hyperf\RpcServer\Annotation\RpcService;

#[RpcService(name: "PhpHttpService", protocol: "jsonrpc-http", server: "jsonrpc-http")]
class PhpHttpService implements PhpServiceInterface
{
    public function add(array $args): array
    {
        $result = [];
        $result["c"] = $args["a"] + $args["b"];
        return $result;
    }
}
  • 客户端代码(以请求java为例)
    • 接口
    <?php
    declare(strict_types=1);
    
    namespace App\JsonRpc;
    
    use Hyperf\RpcClient\AbstractServiceClient;
    
    class JavaHttpServiceConsumer extends AbstractServiceClient implements JavaServiceInterface
    {
        protected string $serviceName = 'JavaHttpService';
    
        protected string $protocol = 'jsonrpc-http';
    
        public function add(array $args): array
        {
            return $this->__request(__FUNCTION__, compact('args'));
        }
    }
    
    • 调用
    <?php
    
    namespace App\Task;
    
    use Hyperf\Contract\StdoutLoggerInterface;
    use Hyperf\Crontab\Annotation\Crontab;
    use Hyperf\Di\Annotation\Inject;
    use App\JsonRpc\JavaHttpServiceConsumer;
    
    #[Crontab(name: "ClientTask", rule: "*/5 * * * * *", callback: "execute", memo: "客户端定时任务")]
    class ClientTask
    {
        #[Inject]
        private StdoutLoggerInterface $logger;
    
        #[Inject]
        private JavaHttpServiceConsumer $javaHttpServiceConsumer;
    
        public function execute()
        {
            $seed = time();
            srand($seed);
            
            try {
                $a = rand(0, 100);
                $b = rand(0, 100);
                $result = $this->javaHttpServiceConsumer->add(["a" => $a, "b" => $b]);
                $this->logger->info(sprintf("[http] PHP asked:\"%d+%d=?\"; Java answered:\"%d\"", $a, $b, $result["c"]));
            } catch (Exception $e) {
                $this->logger->info($e->getMessage());
            }
        }
    }
    

2、Java

  • 服务端代码(以http协议为例)
package io.moonquakes.javahttp.service;

import io.moonquakes.javahttp.dto.ArgsDto;
import io.moonquakes.javahttp.dto.ResultDto;

public class JavaHttpService implements IJavaHttpService {
    @Override
    public ResultDto add(ArgsDto args) {
        ResultDto resultDto = new ResultDto();
        resultDto.setC(args.getA() + args.getB());
        return resultDto;
    }
}
  • 客户端代码(以请求go为例)
    • 接口
    package io.moonquakes.javahttp.client;
    
    import com.sunquakes.jsonrpc4j.JsonRpcClient;
    import com.sunquakes.jsonrpc4j.JsonRpcProtocol;
    import io.moonquakes.javahttp.dto.ArgsDto;
    import io.moonquakes.javahttp.dto.ResultDto;
    
    @JsonRpcClient(value = "GoHttp", protocol = JsonRpcProtocol.http, url = "localhost:3602")
    public interface IGoHttpClient {
        ResultDto Add(ArgsDto args);
    }
    
    • 调用
    package io.moonquakes.javahttp.task;
    
    import io.moonquakes.javahttp.client.IGoHttpClient;
    import io.moonquakes.javahttp.dto.ArgsDto;
    import io.moonquakes.javahttp.dto.ResultDto;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Slf4j
    @EnableScheduling
    @Component
    public class ClientTask {
    
        @Autowired(required = false)
        private IGoHttpClient goHttpClient;
    
        @Scheduled(fixedDelay = 10000)
        public void run() {
            ArgsDto args = new ArgsDto();
            try {
                int a = (int) (Math.random() * 100);
                int b = (int) (Math.random() * 100);
                args.setA(a);
                args.setB(b);
                ResultDto resultDto = goHttpClient.Add(args);
                log.info(String.format("[http] Java asked:\"%d+%d=?\"; Go answered:\"%d\"", a, b, resultDto.getC()));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }
    

3、Go

  • 服务端代码(以http协议为例)
go func() {
s, _ := jsonrpc4go.NewServer("http", "127.0.0.1", "3602")
s.Register(new(GoHttp))
s.Start()
}()
  • 客户端代码(以请求php为例)
phpHttpClient, _ := jsonrpc4go.NewClient("http", "127.0.0.1", "9504")
a = rand.Intn(100)
b = rand.Intn(100)
_ = phpHttpClient.Call("php_http/add", Params{Args{a, b}}, result, false)

四、演示项目

1、简介

moonquakes是一个演示项目。它展示了如何在一些web框架中使用jsonrpc协议进行通信,这些web框架是由java、php或golang编写的。

在moonquakes中,java框架用的是springboot,它使用 jsonrpc4j 与go和php框架通信;php框架是 Hyperf ,它有自己的 jsonrpc组件 来与go和java框架通信;go框架使用 jsonrpc4go 与java和php框架通信。

2、Demo项目地址

  • https://github.com/sunquakes/moonquakes

3、 预览