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

news2024/11/28 14:54:38

文章目录

  • 写在前面
  • 基本使用
  • 封装客户端
  • 封装服务端
  • 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; // 如果都匹配成功,则返回true
        else
            return false; // 否则返回false
    }

    // 尝试插入用户信息,成功返回 true,失败返回 false
    bool 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 function
	void setConnected(bool connect = true){connected_ = connect; }
	bool connected()const{return connected_; }
	void restart();
	//
	// watch class
	class 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.coloradmin.cn/o/1961349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

029-GeoGebra中级篇—一般对象之复数

GeoGebra 支持复数运算和可视化&#xff0c;允许用户在复平面上进行各种操作。用户可以定义复数、进行加减乘除等基本运算&#xff0c;并使用 GeoGebra 的图形工具在复平面上绘制复数的表示&#xff0c;探索复数的几何意义。这使得 GeoGebra 成为学习和研究复数及其应用的有力工…

合并K个有序链表

题目 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例1&#xff1a; 输入&#xff1a; 输出&#xff1a; 示例2&#xff1a; 输入&#xff1a; 输出&#xff1a; 示例3&#xff1a; 输入&…

【Vue3】组件生命周期

【Vue3】组件生命周期 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本…

Java从入门初级开发到精通百万级架构师:全套教程 | 学习路线(免费白嫖)

以下是一篇关于Java编程从入门到精通的文章&#xff0c;旨在帮助初学者和有一定基础的程序员系统地学习Java语言及其应用&#xff1a; Java语言编程从入门到精通&#xff1a;Java从入门到项目实战全套教程 Java作为一种广泛使用的编程语言&#xff0c;拥有强大的生态系统和丰富…

「 LaTeX 」如何修改公式底纹颜色

一、前言 小白在论文返修过程中&#xff0c;需要标注出部分公式的修正&#xff0c;因此用到这个代码指令。 二、技术实现 指令代码如下&#xff1a; \mathcolorbox{yellow}{ TEXT } 三、实例 \begin{figure*} \begin{equation} \centering \begin{aligned}\begin{bmatrix}{…

食家巷胡麻饼酥脆滋味,难以抗拒

在美食的浩瀚星空中&#xff0c;食家巷胡麻饼宛如一颗璀璨的明珠&#xff0c;散发着独特而迷人的魅力。食家巷胡麻饼&#xff0c;那金黄酥脆的外皮&#xff0c;宛如一层精心雕琢的铠甲&#xff0c;闪烁着诱人的光泽。上面点缀着密密麻麻的胡麻籽&#xff0c;犹如繁星点点&#…

终端pip安装包后,Pycharm却导入失败?新手别慌,3招搞定!

很多小伙伴在学习Python的过程中,都会遇到这种情况:明明在终端用pip安装好了需要的包,但在Pycharm中导入时却报错。难道是安装姿势不对? 例如在cmd中已经有了pandas,但是去pycharm中导入pandas显示没有 先别急着怀疑人生,这很可能是因为pip安装包的路径和Pycharm项目使用…

Docker容器下面home assistant忘记账号密码怎么重置?

环境&#xff1a; docker ha 问题描述&#xff1a; Docker容器下面home assistant忘记账号密码怎么重置&#xff1f; 解决方案&#xff1a; 你可以按照以下步骤来找回或重置密码&#xff1a; 方法一 (未解决) 停止并删除当前的Home Assistant容器&#xff08;确保你已经保…

设计模式16-代理模式

设计模式16-代理模式 动机定义与结构模式定义结构 代码推导特点应用总结实例说明1. 远程代理2. 虚拟代理3. 保护代理4. 智能引用代理 动机 在面向对象系统中有一些对象由于某种原因比如对象创建的开销很大或者某些操作需要安全控制&#xff0c;或者需要进程外的访问等情况。直…

Mac电脑流氓软件怎么卸载不了 MacBook删除恶意软件 电脑流氓软件怎么彻底清除

对于Mac用户来说&#xff0c;尽管MacOS系统以其较高的安全性而闻名&#xff0c;但依然不可避免地会遭遇流氓软件或恶意软件的困扰。本文将详细介绍Mac电脑流氓软件怎么卸载&#xff0c;Mac电脑如何移除移除恶意软件&#xff0c;确保你的设备运行安全、流畅。 一、Mac电脑流氓软…

【论文共读】【翻译】【GAN】Generative Adversarial Nets

论文原文地址&#xff1a;https://arxiv.org/pdf/1406.2661 翻译&#xff1a;Generative Adversarial Nets 生成对抗网络 0. 摘要 提出了一种新的对抗过程估计生成模型的框架&#xff0c;其中我们同时训练两个模型&#xff1a;一个是捕获数据分布的生成模型G&#xff0c;另一…

【基础夯实】TCP/IP 协议是怎么控制数据收发

【基础夯实】TCP/IP 协议是怎么控制数据收发 网址输入到页面完整显示&#xff0c;对于此问题&#xff0c;粗略的解释可以分为以下几个步骤&#xff1a; 客户端通过 HTTP 协议对数据进行一次包装通过 DNS 服务器&#xff08;本地无缓存&#xff09;解析网址的 ip 地址通过 TCP…

layui 乱入前端

功能包含 本实例代码为部分傻瓜框架&#xff0c;插入引用layui。因为样式必须保证跟系统一致&#xff0c;所以大部分功能都是自定义的。代码仅供需要用layui框架&#xff0c;但原项目又不是layui搭建的提供解题思路。代码较为通用 自定义分页功能自定义筛选列功能行内编辑下拉、…

【React】详解如何获取 DOM 元素

文章目录 一、基础概念1. 什么是DOM&#xff1f;2. 为什么需要获取DOM&#xff1f; 二、使用 ref 获取DOM元素1. 基本概念2. 类组件中的 ref3. 函数组件中的 ref 三、 ref 的进阶用法1. 动态设置 ref2. ref 与函数组件的结合 四、处理特殊情况1. 多个 ref 的处理2. ref 与条件渲…

跟着丑萌气质狗学习WPF——Style样式

Style样式 1. 用法介绍2. 样式多样性3. 全局样式说明和资源字典的使用 1. 用法介绍 提前写好样式&#xff0c;让他作用于所有按钮 <Window x:Class"WPF_Study_Solution.window3"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmln…

typescript 解构时配置类型

以下三种写法&#xff0c;可以参考&#xff1a; const handleMenuClick ({item, key, keyPath}: {item: Object, key: string, keyPath:string}) > {} const handleMenuClick ({item, key, keyPath}: any) > {} interface SomeObj {item: Objectkey: stringkeyPath:st…

计算机系统操作系统简介

目录 1.计算机系统简介 1.1组成结构 1.2系统软件 1.3冯诺依曼计算机特点 1.4硬件构架 2.硬件的进一步认识 2.1存储器 2.2输入设备 2.3输出设备 2.4CPU组成 2.5线的概念引入 3.操作系统 3.1操作系统简介 3.2操作系统如何管理 3.3库函数和系统调用 1.计算机系统简介…

Linux 用户管理模式

目录 1. 概述 2. 管控级别 3. 用户组管理 4. 用户管理 4.1 创建用户 useradd 4.2 删除用户 userdel ​编辑4.3 查看用户所属组 id 4.4 修改用户所属组 usermod 5. 查看用户/用户组 5.1 查看系统用户 5.2 查看系统用户组 1. 概述 Linux 可以配置多个用户&#xff0c…

ppt中国风背景图片去哪找?附6个优质中国风PPT模板分享!

在这个全球化的时代&#xff0c;中国传统文化元素正在各个领域焕发出新的生机&#xff0c;不管是在时尚、建筑还是平面设计领域&#xff0c;中国风都以其独特的美学魅力吸引着世界的目光。在商业演示和学术报告中&#xff0c;PowerPoint(PPT)作为最常用的工具之一&#xff0c;同…

opencv arm 交叉编译

step1.opencv源码文件夹下新建build-arm目录 step2. cmake图像化配置 cmake-gui .. step3. 选择交叉编译 step4.检索交叉编译链路径 step5. 配置 配置install路径 配置编译、链接选项 添加人脸检测模块 config->generate step6. make编译 built-arm目录下&#xff1a; …