2015年11月4日星期三

.net 分布式锁实现 - 车江毅

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
.net 分布式锁实现 - 车江毅  阅读原文»

分布式锁

经常用于在解决分布式环境下的业务一致性和协调分布式环境。

实际业务场景中,比如说解决并发一瞬间的重复下单,重复确认收货,重复发现金券等。

使用分布式锁的场景一般不能太多。

开源地址:http://git.oschina.net/chejiangyi/XXF.BaseService.DistributedLock

开源相关群: .net 开源基础服务 238543768

这里整理了C#.net关于redis分布式锁和zookeeper分布式锁的实现,仅用于研究。(可能有bug)

采用ServiceStack.Redis实现Redis分布式锁

/*
* Redis分布式锁
* 采用ServiceStack.Redis实现的Redis分布式锁
* 详情可阅读其开源代码
* 备注:不同版本的 ServiceStack.Redis 实现reidslock机制不同 xxf里面默认使用2.2版本
*/ public class RedisDistributedLock : BaseRedisDistributedLock
{
private ServiceStack.Redis.RedisLock _lock;
private RedisClient _client;
public RedisDistributedLock(string redisserver, string key)
:
base(redisserver, key)
{

}

public override LockResult TryGetDistributedLock(TimeSpan? getlockTimeOut, TimeSpan? taskrunTimeOut)
{
if (lockresult == LockResult.Success)
throw new DistributedLockException("检测到当前锁已获取");
_client
= DistributedLockConfig.GetRedisPoolClient(redisserver).GetClient();
/* * 阅读源码发现当其获取锁后,redis连接资源会一直占用,知道获取锁的资源释放后,连接才会跳出,可能会导致连接池资源的浪费。 */

try {
this._lock = new ServiceStack.Redis.RedisLock(_client, key, getlockTimeOut);
lockresult
= LockResult.Success;
}
catch (Exception exp)
{
XXF.Log.ErrorLog.Write(
string.Format("redis分布式尝试锁系统级别严重异常,redisserver:{0}", redisserver.NullToEmpty()), exp);
lockresult
= LockResult.LockSystemExceptionFailure;
}
return lockresult;
}

public override void Dispose()
{
try {
if (this._lock != null)
this._lock.Dispose();
if (_client != null)
this._client.Dispose();
}
catch (Exception exp)
{
XXF.Log.ErrorLog.Write(
string.Format("redis分布式尝试锁释放严重异常,redisserver:{0}", redisserver.NullToEmpty()), exp);
}
}
}

来自网络的java实现Redis分布式锁(C#版)

/*
* Redis分布式锁
* 采用网络上java实现的Redis分布式锁
* 参考
http://www.blogjava.net/hello-yun/archive/2014/01/15/408988.html
* 详情可阅读其开源代码
*/ public class RedisDistributedLockFromJava : BaseRedisDistributedLock
{


public RedisDistributedLockFromJava(string redisserver, string key)
:
base(redisserver, key)
{


}

public override LockResult TryGetDistributedLock(TimeSpan? getlockTimeOut, TimeSpan? taskrunTimeOut)
{
if (lockresult == LockResult.Success)
throw new DistributedLockException("检测到当前锁已获取");
try {
// 1. 通过SETNX试图获取一个lock
         string @lock = key;

long taskexpiredMilliseconds = (taskrunTimeOut != null ? (long)taskrunTimeOut.Value.TotalMilliseconds : (long)DistributedLockConfig.MaxLockTaskRunTime);
long getlockexpiredMilliseconds = (getlockTimeOut != null ? (long)getlockTimeOut.Value.TotalMilliseconds : 0);
long hassleepMilliseconds = 0;
while (true)
{
using (var redisclient = DistributedLockConfig.GetRedisPoolClient(redisserver).GetClient())
{
long value = CurrentUnixTimeMillis() + taskexpiredMilliseconds + 1;
/*Java以前版本都是用SetNX,但是这种是无法设置超时时间的,不是很理解为什么,
              * 可能是因为原来的redis命令比较少导致的?现在用Add不知道效果如何.
                因对redis细节不了解,但个人怀疑若异常未释放锁经常发生,可能会导致内存逐步溢出
*/

              bool acquired = redisclient.Add<long>(@lock, value, TimeSpan.FromMilliseconds(taskexpiredMilliseconds + DistributedLockConfig.TaskLockDelayCleepUpTime));
//SETNX成功,则成功获取一个锁 你真的会玩SQL吗?表表达式,排名函数 - 欢醉  阅读原文»

这次讲的有些可能是经常用但不会注意到,所以来统一总结一下用法。

我们往往需要临时存储某些结果集。除了用临时表和表变量,还可以使用公用表表达式的方法。

表表达式

    1. 期待单个值的地方可以使用标量子查询
    2. 期待多个值的地方可以使用多值子查询
    3. 在期待出现表的地方可用表值子查询表表达式

1.派生表

是从查询表达式派生出虚拟结果表的表表达式,派生表的存在范围只是外部查询。

使用形式:from 派生表 as 派生表列名

规则:

    1. 所有列必须有名称
    2. 列名必须唯一
    3. 不允许使用order by(除非指定了top)

不同于标量和多值子查询,派生表不能是相关的,它必须是独立的。

2.公用表表达式(CTE)

非递归公用表表达式(CTE)是查询结果仅仅一次性返回一个结果集用于外部查询调用。

WITH CTE_Test
  
AS
  (
  
SELECT * FROM Person_1
  )
  
SELECT * FROM CTE_Test AS a  --第一次引用
  INNER JOIN CTE_Test AS b    --第二次引用
  ON a.Id = b.Id
  
ORDER BY a.Id DESC
--
SELECT * FROM CTE_Test 再查询一次会报错

递归公用表达式

来引用他人的一个示例:

先建一张表栏目表如下,栏目Id,栏目名称,栏目的父栏目

现在使用CTE查询其每个栏目是第几层栏目的代码如下:

WITH COL_CTE(Id,Name,ParentId,tLevel )
AS
(
--基本语句
SELECT Id,Name,ParentId,0 AS tLevel FROM Col
WHERE ParentId = 0
UNION ALL
--递归语句
SELECT c.Id,c.Name,c.ParentId,ce.tLevel+1 AS tLevel FROM COL as c
INNER JOIN COL_CTE AS ce   --递归调用
ON c.ParentId = ce.Id
)

SELECT * FROM COL_CTE

结果:

0表示顶级栏目。1就是1级栏目

排名函数

四个排名函数:

  1.row_number

  2.rank

  3.dense_rank

  4.ntile 

排名函数order by子句是必需的。我们这里不讲定义,直接讲实例用法。

利用row_number生成连续行号

SELECT empid ,
qty ,
ROW_NUMBER()
OVER ( ORDER BY qty ) AS rownum
FROM sales
ORDER BY qty

小的分组范围内排序通过PARTITION BY选项来重新排序,给数据分区或者数据区域唯一的递增序号

如:LastName以‘A’开头的作为第一组,在这个组内进行排序。以‘B’开头的作为第二组,在这个组内排序。以‘C’开头的作为第三组,在这个组内进行排序,如此等等

select
ROW_NUMBER()
over(PARTITION by substring(LastName,1,1) order by LastName) as RowNum,
FirstName
+' '+ LastName as FullName
from HumanResources.vEmployee

结果

假设LastName以‘A’开头的是男子组,这个组有共有三个人,Kim Abercrombie是冠军,Jay Adams是亚军,Nancy Anderson是季军。假设LastName以‘B’开头的是女子组,这个组只有一个人Bryan Baker,无论如何她都是冠军。等等如此类推。这样一眼就能看出他们的小组名次了。

RANK

果有同时撞线的情况发生应该怎么计名次呢?例如A第一个撞线,B和C同时第二个撞线,D第三个撞线,如果我们想把D的名次计为第4名应该怎么处理呢?就是说不计顺序名次,只计人数。这时就可以使用RANK函数了。

在order by子句中定义的列上,如果返回一行数据与另一行具有相同的值,rank函数将给这些行赋予相同的排名数值。在排名的过程中,保持一个内部计数值,当值有所改变时,排名序号将有一个跳跃。

SELECT ROW_NUMBER() OVER ( ORDER BY Department ) AS RowNum ,
RANK()
OVER ( ORDER BY Department ) AS Ranking ,
FirstName
+ ' ' + LastName AS FullName ,
Department
FROM HumanResources.vEmployeeDepartment
阅读更多内容

没有评论:

发表评论