项目:基于gRPC进行项目的微服务架构改造

文章目录

  • 写在前面
  • 基本使用
  • 封装客户端
  • 封装服务端
  • Zookeeper

写在前面

最近学了一下gRPC进行远程调用的原理,所以把这个项目改造成了微服务分布式的架构,今天也是基本实现好了,代码已提交

在这里插入图片描述
这里补充一下文档吧,也算记录一下整个过程

基本使用

gRPC首先在安装上就非常繁琐,网络的教程也比较多,但要注意安装的版本兼容性问题,尤其是对应的Protubuf和gRPC的版本,同时要注意,在进行编译的时候要使用cmake进行编译,我最开始使用的是传统的Makefile,因为项目最开始用的就是这种,所以就直接使用了,而在进行编译链接的时候总是报错:

在这里插入图片描述
最后去查阅了官方文档,也就是gRPC的维护者,文档提示最好使用cmake进行编译:

在这里插入图片描述
https://github.com/grpc/grpc/tree/master/src/cpp

用了cmake就不会报链接的错误了,总体来说,gRPC安装确实繁琐,需要细心一点

使用的命令也比较简单:

protoc --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` message.proto
protoc --cpp_out=. message.proto

一个是生成grpc文件的,一个是生成proto文件的

下面我以一个服务为演示吧,其他的服务基本差不多,我就不再演示了

封装客户端

首先,对于客户端进行封装:

#pragma once
#include "const.h"
#include "Singleton.h"
#include "ConfigMgr.h"
#include <condition_variable>
#include <grpcpp/grpcpp.h> 
#include <queue>
#include "message.grpc.pb.h"
#include "message.pb.h"using grpc::Channel;
using grpc::Status;
using grpc::ClientContext;using message::LoginRsp;
using message::LoginReq;
using message::StatusService;/*** @brief 管理 gRPC 客户端的连接池,用于与 StatusService 通信* */
class StatusConPool 
{
public:StatusConPool(size_t poolSize, std::string host, std::string port): poolSize_(poolSize), host_(host), port_(port), b_stop_(false) {for (size_t i = 0; i < poolSize_; ++i) {std::shared_ptr<Channel> channel = grpc::CreateChannel(host + ":" + port,grpc::InsecureChannelCredentials());connections_.push(StatusService::NewStub(channel));}}~StatusConPool() {std::lock_guard<std::mutex> lock(mutex_);Close();while (!connections_.empty()) {connections_.pop();}}std::unique_ptr<StatusService::Stub> getConnection(){std::unique_lock<std::mutex> lock(mutex_);cond_.wait(lock, [this] {if (b_stop_) {return true;}return !connections_.empty();});//如果停止则直接返回空指针if (b_stop_) {return  nullptr;}auto context = std::move(connections_.front());connections_.pop();return context;}void returnConnection(std::unique_ptr<StatusService::Stub> context) {std::lock_guard<std::mutex> lock(mutex_);if (b_stop_) {return;}connections_.push(std::move(context));cond_.notify_one();}void Close(){b_stop_ = true;cond_.notify_all();}private:atomic<bool> b_stop_;size_t poolSize_;std::string host_;std::string port_;std::queue<std::unique_ptr<StatusService::Stub>> connections_;std::mutex mutex_;std::condition_variable cond_;
};/*** @brief 通过单例模式实现的 gRPC 客户端,用于向 StatusService 发送请求* */
class StatusGrpcClient :public Singleton<StatusGrpcClient>
{friend class Singleton<StatusGrpcClient>;
public:~StatusGrpcClient() {}LoginRsp Login(string username, string password){ClientContext context;LoginRsp reply;LoginReq request;request.set_username(username);request.set_password(password);auto stub = pool_->getConnection();cout << "准备进行grpc 发送了" << endl;Status status = stub->Login(&context, request, &reply);Defer defer([&stub, this]() {pool_->returnConnection(std::move(stub));});if (status.ok()) {return reply;}else {reply.set_error(ErrorCodes::RPCFailed);return reply;}}
private:StatusGrpcClient(){auto& gCfgMgr = ConfigMgr::Inst();std::string host = gCfgMgr["StatusServer"]["Host"];std::string port = gCfgMgr["StatusServer"]["Port"];cout << "host:port" << host + ":" + port << endl;pool_.reset(new StatusConPool(5, host, port));}std::unique_ptr<StatusConPool> pool_;
};

在这样进行封装了之后:

在这里插入图片描述

由于这里存在对应的接口,此时就能够进行远程调用了,然后对于远程调用回来的结果进行判断即可

封装服务端

gRPC比较优秀的一点就在于,它能够屏蔽网络的传输,使得使用者可以专注的对于业务逻辑进行处理,具体可以看下面这个:

在这里插入图片描述
这里proto会生成一个服务类,这个类是一个虚基类,只需要对于这个类进行继承后,实现对应的接口,那么在进行调用的时候就可以去调用我们实际要进行处理的逻辑,就是一个多态的思想:

#pragma once
#include <grpcpp/grpcpp.h>
#include "message.grpc.pb.h"
#include <mutex>
#include "ConfigMgr.h"
#include "MysqlMgr.h"
#include "const.h"
#include "RedisMgr.h"
#include <climits>
#include <nlohmann/json.hpp>
#include <regex>using grpc::ServerContext;
using grpc::Status;using message::LoginReq;
using message::LoginRsp;
using message::RegReq;
using message::RegRsp;
using message::StatusService;
using json = nlohmann::json;class StatusServiceImpl final : public StatusService::Service
{
public:StatusServiceImpl(){}Status Login(ServerContext* context, const LoginReq* request, LoginRsp* reply){cout << "收到了 Login" << endl;auto username = request->username();auto password = request->password();bool success = authenticate(username.c_str(), password.c_str());cout << "验证成功" << endl;if(!success){reply->set_error(ErrorCodes::PasswdErr);cout << "发送成功" << endl;return Status::OK;}reply->set_error(ErrorCodes::Success);cout << "发送成功" << endl;return Status::OK;}// 验证用户名和密码是否正确bool authenticate(const char *username, const char *password){cout << "去Redis里面看看" << endl;if(FindInRedis(username, password))return true;cout << "去Mysql里面看看" << endl;return MysqlMgr::GetInstance()->CheckPwd(username, password);}bool FindInRedis(const char* username, const char* password){string result = RedisMgr::GetInstance()->HGet("user:username:password", username);return result == password;}Status Register(ServerContext* context, const RegReq* request, RegRsp* reply){auto username = request->username();auto password = request->password();bool success = RegisterInfo(username.c_str(), password.c_str());if(!success){reply->set_error(ErrorCodes::PasswdErr);return Status::OK;}reply->set_error(ErrorCodes::Success);return Status::OK;}bool validateCredentials(const string& username, const string& password) {// 定义用户名的正则表达式regex usernamePattern("^[a-zA-Z0-9._-]{3,}$");// 定义密码的正则表达式regex passwordPattern("^[a-zA-Z0-9._-]{6,}$");// 使用regex_match进行匹配,注意这里应该是&&操作,因为两个条件都需要满足if(regex_match(username, usernamePattern) && regex_match(password, passwordPattern))return true; // 如果都匹配成功,则返回trueelsereturn false; // 否则返回false}// 尝试插入用户信息,成功返回 true,失败返回 falsebool RegisterInfo(const char *username, const char *password){if(!validateCredentials(username, password))return false;return MysqlMgr::GetInstance()->RegUser(username, password);}
};

这样,在外部服务端,就可以进行调用了:

在这里插入图片描述

Zookeeper

分布式架构当中存在一个有用的组件,Zookeeper,这个原理是进行一个类似于文件系统的架构,然后可以进行读取其中的值,并且还设置了对应的回调函数,也就是所谓的Watcher,发现有服务到达的时候,就执行对应的回调函数,那么基于这个原理,就可以去动态识别到gRPC的服务

gRPC的服务我也封装好了,其他的就看仓库里面的代码吧

#ifndef _ZOOKEEPER_H_
#define _ZOOKEEPER_H_#include <zookeeper/zookeeper.h>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <typeinfo>typedef boost::function<void (const std::string &path, const std::string &value)> DataWatchCallback;
typedef boost::function<void (const std::string &path, const std::vector<std::string> &value)> ChildrenWatchCallback;
//
class ZkRet
{friend class ZooKeeper;
public:bool ok() const {return ZOK == code_; }bool nodeExist() const {return ZNODEEXISTS == code_; }bool nodeNotExist() const {return ZNONODE == code_; }operator bool() const {return ok(); }
protected:ZkRet(){code_ = ZOK; }ZkRet(int c){code_ = c; }
private:int code_;
};
// class Zookeeper, 
// thread safety: single ZooKeeper object should be used in single thread.
class ZooKeeper : public boost::noncopyable
{
public:ZooKeeper();~ZooKeeper();//ZkRet init(const std::string &connectString);ZkRet getData(const std::string &path, std::string &value);ZkRet setData(const std::string &path, const std::string &value);ZkRet getChildren(const std::string &path, std::vector<std::string> &children);ZkRet exists(const std::string &path);ZkRet createNode(const std::string &path, const std::string &value, bool recursive = true);// ephemeral node is a special node, its has the same lifetime as the session ZkRet createEphemeralNode(const std::string &path, const std::string &value, bool recursive = true);// sequence node, the created node's name is not equal to the given path, it is like "path-xx", xx is an auto-increment number ZkRet createSequenceNode(const std::string &path, const std::string &value, std::string &rpath, bool recursive = true);ZkRet createSequenceEphemeralNode(const std::string &path, const std::string &value, std::string &rpath, bool recursive = true);ZkRet watchData(const std::string &path, const DataWatchCallback &wc);ZkRet watchChildren(const std::string &path, const ChildrenWatchCallback &wc);//void setDebugLogLevel(bool open = true);//ZkRet setFileLog(const std::string &dir = "./");ZkRet setConsoleLog();//static std::string getParentPath(const std::string &path);static std::string getNodeName(const std::string &path);static std::string getParentNodeName(const std::string &path);
private:// for inner use, you should never call these functionvoid setConnected(bool connect = true){connected_ = connect; }bool connected()const{return connected_; }void restart();//// watch classclass Watch{public:Watch(ZooKeeper *zk, const std::string &path);virtual void getAndSet() const = 0;const std::string &path() const{return path_; }ZooKeeper* zk() const {return zk_; }protected:ZooKeeper *zk_;std::string path_;};typedef boost::shared_ptr<Watch> WatchPtr;class DataWatch: public Watch{public:typedef DataWatchCallback CallbackType;DataWatch(ZooKeeper *zk, const std::string &path, const CallbackType &cb);virtual void getAndSet() const;void doCallback(const std::string &data) const{ cb_ (path_, data); };private:CallbackType cb_;};class ChildrenWatch: public Watch{public:typedef ChildrenWatchCallback CallbackType;ChildrenWatch(ZooKeeper *zk, const std::string &path, const CallbackType &cb);virtual void getAndSet() const;void doCallback(const std::vector<std::string> &data) const { cb_ (path_, data); };private:CallbackType cb_;};//class WatchPool{public:template<class T>WatchPtr createWatch(ZooKeeper *zk, const std::string &path, const typename T::CallbackType &cb){std::string name = typeid(T).name() + path;WatchMap::iterator itr = watchMap_.find(name);if(watchMap_.end() == itr){WatchPtr wp(new T(zk, path, cb));watchMap_[name] = wp;return wp;}else{return itr->second;}}template<class T>WatchPtr getWatch(const std::string &path){std::string name = typeid(T).name() + path;WatchMap::iterator itr = watchMap_.find(name);if(watchMap_.end() == itr){return WatchPtr();}else{return itr->second;}}//void getAndSetAll() const{for(WatchMap::const_iterator it = watchMap_.begin(); it != watchMap_.end(); ++it){it->second->getAndSet();}}private:typedef std::map<std::string, WatchPtr> WatchMap;WatchMap watchMap_;};//static void dataCompletion(int rc, const char *value, int valueLen, const struct Stat *stat, const void *data);static void stringsCompletion(int rc, const struct String_vector *strings, const void *data);static void defaultWatcher(zhandle_t *zh, int type, int state, const char *path,void *watcherCtx);//ZkRet createTheNode(int flag, const std::string &path, const std::string &value, char *rpath, int rpathlen, bool recursive);//void miliSleep(int milisec);//zhandle_t *zhandle_;std::string connectString_;bool connected_;ZooLogLevel defaultLogLevel_;WatchPool watchPool_;//FILE *logStream_;
};#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/386981.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ruoyi 快速开发平台

Ruoyi 快速开发平台 一、官网二、准备工作2.1 环境要求2.2 必要配置 三、运行系统3.1 后端运行3.2 前端安装及运行 四、自定义开发4.1 新增业务模块4.2 代码生成4.2.1 创建菜单4.2.2 后端代码4.2.3 前端代码 一、官网 链接: 前后端分离版本 回到目录 二、准备工作 2.1 环境要…

【C语言】链式队列的实现

队列基本概念 首先我们要了解什么是队列&#xff0c;队列里面包含什么。 队列是线性表的一种是一种先进先出&#xff08;First In Fi Out&#xff09;的数据结构。在需要排队的场景下有很强的应用性。有数组队列也有链式队列&#xff0c;数组实现的队列时间复杂度太大&#x…

【数据结构】链式二叉树的实现和思路分析及二叉树OJ

【数据结构】链式二叉树的实现和思路分析及二叉树OJ &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;数据结构 文章目录 【数据结构】链式二叉树的实现和思路分析及二叉树OJ前言一.链式二叉树的定义及结构二.链式二叉树的遍历2.1前序遍历2.2中…

Typora 【最新1.8.6】版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora 是一款专为 Markdown 爱好者设计的文本编辑器&#xff0c;它结合了简洁的界面设计与强大的 Markdown 渲染能力&#xff0c;为用户提供了一个流畅、高效的写作环境。以下是对 Typora 更详细的介绍&#xff1a; 核心特…

课程学习前提约束(拓扑排序练习)

很显然的拓扑排序 class Solution { public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {int n numCourses;vector<int> record(n1,0);queue<int> q;vector<vector<int>> graph(n 1);for (int i 0; i &l…

nfs和web服务器的搭建

&#xff08;一&#xff09;web服务器的搭建 1.配置基本环境 要点有&#xff0c;yum源&#xff0c;包含nginx和阿里云&#xff08;或者腾讯云或者华为云&#xff09;&#xff0c;这里的相关知识可以参考之前的yum配置笔记 2.安装nginx yum -y install nginx 3.验证并且开启服…

源码编译安装,及nginx服务控制、监控块

1.源码编译安装&#xff1a; [root17dns ~]# wget https://nginx.org/download/nginx-1.27.0.tar.gz 2.解压&#xff1a; [root17dns ~]# tar -zxvf nginx-1.27.0.tar.gz 3.安装gcc等工具 [root17dns ~]# yum -y install gcc gcc-c [root17dns ~]# yum -y install make lrzsz …

24年第三届钉钉杯大学生大数据挑战赛浅析

需要完整资料&#xff0c;请关注WX&#xff1a;“小何数模”&#xff01; 本次钉钉杯大数据挑战赛的赛题已正式出炉&#xff0c;无论是赛题难度还是认可度&#xff0c;该比赛都是仅次于数模国赛的独一档&#xff0c;可以用于国赛前的练手训练。考虑到大家解题实属不易&#xf…

如何学习Doris:糙快猛的大数据之路(从入门到专家)

引言:大数据世界的新玩家 还记得我第一次听说"Doris"这个名字时的情景吗?那是在一个炎热的夏日午后,我正在办公室里为接下来的大数据项目发愁。作为一个刚刚跨行到大数据领域的新手,我感觉自己就像是被丢进了深海的小鱼—周围全是陌生的概念和技术。 就在这时,我的…

Django实战:开启数字化任务管理的新纪元

&#x1f680; Django实战&#xff1a;开启数字化任务管理的新纪元 &#x1f310; &#x1f4d6; 引言 在数字化转型的浪潮中&#xff0c;任务管理的智能化成为提升组织效能的关键。今天&#xff0c;我将带领大家深入了解我们最新开发的OFTS系统——一款创新的组织任务管理软…

双指针-【3,4,5,6,7,8】

第三题&#xff1a;快乐数 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/happy-number/算法思想&#xff1a; 1.每个…

SpringBoot上传超大文件导致OOM,完美解决办法

问题描述 上传大文件报错: Caused by: java.lang.OutOfMemoryError at java.io.ByteArrayOutputStream.hugeCapacity(ByteArrayOutputStream.java:123) ~[?:1.8.0_381] at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:117) ~[?:1.8.0_381] …

调用百度的大模型API接口实现AI对话!手把手教程!

本文介绍如何使用百度的大模型API接口实现一个AI对话项目 1 注册百度云 2 获取API接口 3 配置环境 4 代码编写与运行 5 chat models 1 注册百度云 搜索百度云&#xff0c;打开官网注册&#xff0c;充值一点点大米&#xff08;收费很低&#xff0c;大概生成几个句子花费一毛…

FRP配置内网穿透52版本以上适用

简述 适用frp配置内网穿透来说我们需要进行简单的区分&#xff0c;具有公网IP的服务器我们简称为服务端&#xff0c;内网的服务器我们可以简称为客户端&#xff0c;frp需要针对不同的服务器配置不同的文件 下载安装包 Linux下载地址 https://github.com/fatedier/frp/relea…

好的STEM编程语言有哪些?

STEM是科学&#xff08;Science&#xff09;&#xff0c;技术&#xff08;Technology&#xff09;&#xff0c;工程&#xff08;Engineering&#xff09;&#xff0c;数学&#xff08;Mathematics&#xff09;四门学科英文首字母的缩写&#xff0c;STEM教育简单来说就是在通过在…

如何通过✅ IPIDEA代理IP,轻松实现数据采集和市场拓展工作(下)

如何通过✅ IPIDEA代理IP&#xff0c;轻松实现数据采集和市场拓展工作 如何通过✅ IPIDEA代理IP&#xff0c;轻松实现数据采集和市场拓展工作前言IPIDEA爬虫实战实战Demo演示总结 如何通过✅ IPIDEA代理IP&#xff0c;轻松实现数据采集和市场拓展工作 前言 在当今全球化市场的…

微信小游戏之三消(三)道具相关方法

设计一个 game class。负责了游戏的核心控制逻辑&#xff0c;包括游戏状态管理、方块和道具的生成与效果处理&#xff0c;以及游戏的重新开始和复活流程。通过这些方法&#xff0c;脚本实现了游戏的基本玩法和用户交互。 主要游戏控制方法 gameStart()&#xff1a;开始游戏&am…

MySQL常见指令

MySQL中的数据类型 大致分为五种&#xff1a;数值&#xff0c;日期和时间&#xff0c;字符串&#xff0c;json&#xff0c;空间类型 每种类型也包括也一些不同的子类型&#xff0c;根据需要来选择。 如数值类型包括整数类型和浮点数类型 整数类型根据占用的存储空间的不同 又…

Cocos Creator2D游戏开发(7)-飞机大战(5)-让子弹飞

飞机大战(5)-碰撞及积分 参考敌机的生成 子弹由飞机生成,放在player_node节点子弹重复使用,要使用预制体;子弹新增了动画 ①创建一个预制体 命名为playerBullet_prefab ② 双击预制体将bullet1图片拖入预制体 保存,关闭(场景编辑器里面的) ③ 发射子弹 player加入代码 prop…