C++基于redis的分布式锁

≡放荡痞女 提交于 2019-12-07 12:12:47

之前无意间看到了一下redis的分布式锁,都没有C++版本的,基本全是java的redission。 闲着没事就写了一个。

以前还以为是redis提供的分布式锁的服务,其实不然,只是redis提供了分布式锁的几个基本特性的服务。

    1.是客户端持有锁有时间限制,redis对每个key都可以设置过期时间,所以就很方便去控制锁的过期。

     2.redis有发布和订阅的服务, 这样任意一个客户端在解锁的时候就可以用过发布的方式去通知所有客户端可以抢锁了。

以下是一个简单的版本, 基本功能是有的,后面具体项目要用的完善。我用的时redisclient的这个库文件,这个库网络I/O用的asio,需要编译的时候需要boost。下载地址:https://download.csdn.net/download/lc_boyi/10845732

DistributeLock.h

#ifndef __DISTRBUTELOCK_H_
#define __DISTRBUTELOCK_H_

#include <thread>
#include <iostream>
#include <windows.h>

#include "boost/asio/io_service.hpp"  
#include "boost/asio/ip/address.hpp" 
#include "boost/function.hpp"
#include "config.h"
#include "redisasyncclient.h"
#include "redissyncclient.h"

#define REDIS_HOST "192.168.10.21"
#define REDIS_PORT 6379
#define REDIS_USER "auth"
#define REDIS_PSW "game@t6game"

#define LOCK_KEY_NAME(x) x+"_key"
#define CLIENT_LOCK_TIME 1000*60
class DistributeLock
{
public:

	DistributeLock(void)
	{
		m_signle = CreateEvent(NULL, TRUE, FALSE, NULL);
		m_threadHandle = new std::thread(&DistributeLock::Process, this);
	}

	~DistributeLock(void)
	{
	}

	bool Init(boost::asio::io_service* io_service)
	{
		std::string errmsg;
		RedisValue result;
		//client
		{
			m_client = new RedisSyncClient(*io_service);
			assert(m_client);	
			if (!m_client->connect(boost::asio::ip::address::from_string(REDIS_HOST), REDIS_PORT, errmsg))  
			{
				std::cerr << "Can't connect to redis: " << errmsg << std::endl;
				return false;
			}
			result = m_client->command(REDIS_USER,REDIS_PSW); 
			result = m_client->command("select", "0");
			assert(result.isOk());	
		}

		//client_lock
		{
			m_lock_client = new RedisSyncClient(*io_service);
			assert(m_lock_client);
			if (!m_lock_client->connect(boost::asio::ip::address::from_string(REDIS_HOST), REDIS_PORT, errmsg))  
			{
				std::cerr << "Can't connect to redis: " << errmsg << std::endl;
				return false;
			}
			result = m_lock_client->command(REDIS_USER,REDIS_PSW); 
			result = m_lock_client->command("select", "1");
			assert(result.isOk());	
		}

		//async_client_channal
		{
			m_channal_client = new RedisAsyncClient(*io_service);
			assert(m_channal_client);
			m_channal_client->connect(boost::asio::ip::address::from_string(REDIS_HOST), REDIS_PORT, boost::bind(&DistributeLock::callback_connect, this, _1, _2));
		}

		static int i = 0;
		m_ThreadName = "thread_"+std::to_string(i++);
		return true;
	}

	void callback_connect(bool bSuccess, const std::string &msg)
	{
		std::cout<<m_ThreadName<<"async_client connect success"<<std::endl;
		m_channal_client->command("auth","game@t6game", boost::bind(&DistributeLock::callback_auth, this, _1));
	}

	void callback_auth(const RedisValue &ret)
	{
		if(!ret.isOk())
			std::cout<<m_ThreadName <<": auth error"<<std::endl;

		//通知线程初始化完成
		SetEvent(m_signle);
	}

	void callback_command(const RedisValue &ret)
	{
		if(!ret.isOk())
			std::cout<<m_ThreadName <<": this command isnt ok"<<std::endl;
	}

	void callback_subscribe(const std::vector<char> &msg)
	{
		//通知线程可以开始抢锁了
		SetEvent(m_signle);
	}

	void Process()
	{
		//等待异步连接的初始化完成
		WaitForSingleObject(m_signle, INFINITE);

		//这里对redis的某一个数据进行操作、 还有其他N个线程也在同时操作这个变量
		std::string sKeyName = "NICE";
		int num = 50;
		while(num--)
		{
			redis_lock(sKeyName);
			RedisValue ret = m_client->command("get", sKeyName);
			if(!ret.isOk())
			{
				redis_unlock(sKeyName);
				break;
			}

			int temp = atoi(ret.toString().c_str());
			m_client->command("set", sKeyName, std::to_string(temp + 1));
			std::cout<<m_ThreadName << " SetValue: "<<temp+1<<std::endl;

			redis_unlock(sKeyName);
		}
	}
	
	int redis_lock(std::string &key)
	{
		while(true)
		{
			RedisValue ret = m_lock_client->command("setnx", key, std::to_string(1));
			if(ret.toInt())
			{
				ret = m_lock_client->command("pexpire", key, std::to_string(CLIENT_LOCK_TIME));
				return 0;
			}

			if(m_channal_client->isConnected())
			{
				m_channal_client->singleShotSubscribe(LOCK_KEY_NAME(key), 
					boost::bind(&DistributeLock::callback_subscribe, this, _1), 
					boost::bind(&DistributeLock::callback_command, this, _1));
				WaitForSingleObject(m_signle, CLIENT_LOCK_TIME);
				continue;
			}
			return -1;
		}
		
	}

	int redis_unlock(std::string &key)
	{
		RedisValue ret = m_lock_client->command("del", key);

		//通知其他正在抢锁的线程/进程/服务
		ret = m_lock_client->command("publish", LOCK_KEY_NAME(key), std::to_string(1));
		return 1;
	}

	void JoinThraed(){assert(m_threadHandle);m_threadHandle->join();};
private:
	std::thread *m_threadHandle;
	HANDLE		m_signle;//只有第一次用于等待连接成功。  后面都是用于解锁通知
	RedisSyncClient* m_lock_client;//lock 用于抢锁的redis连接
	RedisAsyncClient* m_channal_client;//channel  用于接收解锁的消息的连接。  需要异步接收
	
	RedisSyncClient* m_client;//业务连接。  也可以是mysql等其他的数据存储服务。
	std::string m_ThreadName;
};
#endif

 main.c

#include <iostream>
#include "DistributeLock.h"

int main()
{
    boost::asio::ip::address add  = boost::asio::ip::address::from_string("192.168.10.21");
	boost::asio::io_service * IOserver= new boost::asio::io_service();
		
	const int N = 10;
	DistributeLock pth[N];
	for(int i = 0; i < N; i++)
	{
		assert(pth[i].Init(IOserver));		
	}

    //这个线程使用与asio的i/o的。 因为使用的异步,如果没有异步这不需要这个线程
	std::thread p(run, IOserver);

	for(int i = 0; i < N; i++)
	{
		pth[i].JoinThraed();
	}
	p.join();
	
    return 0;
}

 

验证结果没有在代码里面去输出,附加的有其他库文件,所以就删了。 大家可以下一个redisclient客户端去查看db_0 "NICE"的值。 一共10个线程,每个线程执行50次,不出意外应该就是500。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!