什麼是分散式鎖?對於這個問題,相信很多同學是既熟悉又陌生。隨著分散式系統的快速發展與廣泛應用,針對共用資源的互斥訪問也成為了很多業務必須要面對的需求,這個場景下人們通常會引入分散式鎖來解決問題。我們通常會使用怎麼樣的分佈鎖服務呢?有開源的 MySQL,Redis,ZooKeeper,Etcd 等三方... ...
一、背景
隨著用戶的增長和業務的增多,單節點服務已經滿足不了需求,用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框架通信。