C++基于redis的分布式锁

之前无意间看到了一下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)
		{
            //old 这种模式可能会造成死锁,也就是执行setnx后,pexpire只前进程宕掉了。
			/*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;
			}*/
            //new 新版set可直接设置过期时间, 加上“NX”后和 setnx命名一样。
            RedisValue ret = m_lock_client->command("set", key, "EX", std::to_string(CLIENT_LOCK_TIME), std::to_string(1), "NX");
            if(ret.toInt())
            {
                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。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值