info clients :查看当前连接的客户端数
config get maxclients :查看redis允许的最大连接数
前言:
redis事务和传统关系型数据库的事务还有区别。
传统关系型数据库会告诉数据库服务器:开始事务,然后执行一系列的读写操作,然后提交事务或回滚。
在redis中,有简单的方法可以控制多个读写命令按顺序执行并相互保持一致。通过multi命令开始事务,发送一系列的命令,执行exec。在exec命令之前,不会做任何处理。
redis命令都具有原子性,包括一次处理多项事情的命令。对于使用多个命令,redis支持事务功能。
redis实际上是单线程运行的,这就是为什么每个redis命令都能保证具有原子性,当一个命令在执行时,没有其他命令会运行。
事务特点:
- inrc 实际上是一个get命令后紧随一个set命令
- getset 设置一个新的值,然后返回原始值
- setnx(SET IF NOT EXISTS) 先测试关键字是否存在,当关键字不存在时就进行值的设置
注意: 虽然redis是单线程运行的,但是我们可以同时运行多个redis客户端进程。如此多个客户端进程操作同一个数据就会有问题。 通过添加watch来解决。监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将会执行失败。
- 事务中的命令将会按顺序地被执行
- 事务中的命令将会如单个原子操作般被执行(中途不会有其他客户端命令被执行)
- 事务中的命令要么全部被执行,要么不会执行
常见的场景就是 购物付费。
multi/exec的延迟执行可以提高性能:
multi命令后,所有的命令都会放入队列中,直到exec命令才会执行。同时客户端会等待所有命令执行完后返回的结果。通过减少与服务器的交互次数,这种命令管道(pipelining)的方式能够提高redis的性能。
购物付费案例:
1.定义用户和库存
使用Hash来存储用户信息。每个用户的库存清单都使用Set来保存唯一的ID。如图:
商城的需求很简单:用户能展示商品和商品价格,当其他用户付钱时,卖家就会收到钱。为了记录足够的信息,我们要关联货品ID和卖货品的人,通过ZSET集合来记录该信息,score就是货品卖出的价钱。
2.在市场列出要出售的货品
在列表程序中,我们需要用到watch命令,结合multi和exec命令来使用。还有unwatch和discard命令。当我们用watch命令来监视任何key时,在我们执行exec命令之前,任何时候有客户端替换,更新或删除了这个key,操作都会失败。通过使用watch,multi/exec,unwatch/discard这些命令,可以保证操作数据意外中断时不会出现问题。
在执行watch命令后,multi命令之前,执行unwatch命令会重置我们的连接。在multi之后,exec命令之前执行discard命令也会重置连接。
也就是说,如果我们监视了某个或某些key,我们能够通过这两个命令取消监视,并且清除命令队列里的待执行命令。
程序清单:添加产品到超市的zset集合中,通过watch监视卖家的库存清单,来保证产品任然有效。
function list_item(conn,itemid,sellerid,price){在一系列初始化工作之后,我们就告诉redis要监视库存inventory集合中的数据,验证用户是否还能卖这些货物,如果能卖,就将这些货物添加到超市,同时从库存表里删除这条信息。如果在监视期间,这条库存记录有改动,就会报错。
var inventory = "inventory:"+sellerid;//market set
var item =itemid+":"+sellerid; //market zset
end = new Date().getTime()+5;
pipe = conn.pipeline();
while(new Date().getTime < end){
try{
pipe.watch(inventory);//监视inventory中的数据变化
if(!pipe.sismember(inventory,itemid)){//查看库存集合中是否存在
pipe.unwatch(inventory);//如果库存中没有,停止监视这条库存信息
return null;
}
pipe.multi();
pipe.zadd("market:",item,price);//将用户库存的商品放入超市
pipe.srem(inventory,itemid);
pipe.execute();
return true;
}catche(Exception e){
}
return false;
}
}如果user17要以97刀的价格卖itemM,需要执行的一系列操作如下:
- watch("inventory:17") 监视库存表
- sismember("inventory:17","itemM"),查看商品是否还能卖
- zadd("market","item:17",97) 添加产品到超市
- srem("inventory:17","itemM") 删除库存中的记录
3.给已买的货品付费
为了付款,首先我们要监视商城(market)中的信息,和买这个产品的用户信息。然后获取用户的总资金和购买产品的价钱,并且验证买家有足够的金额。
为什么redis不实现传统的锁机制?
当修改传统关系型数据库时,关系型数据库会给操作的行记录添加锁,直到事务结束commit或rollback。任何其他的客户端尝试修改数据时,都会阻塞,直到事务提交。可能造成潜在的长时间的等待。
由于redis的目的是最小化等待时间,所以在监视(watch)过程中,redis不会锁定数据。替代的是redis会提醒客户端,有人修改了数据,也就是我们常说的乐观锁。
4.Non-transactional pipelines
在multi和exec命令执行期间,其他客户端不能做任何事。
通过使用pipeline技术(不添加事务),可以提高redis命令执行效率。
在redis中,本身就有一些命令可以接收处理多个参数,如 mget,mset,hmget,hmset,rpush/lpush,sad,zadd。这些命令是为了简化一些相同的重复操作。
使用非事务的管道执行技术也能提升一部分性能,该技术允许我们同时运行一些列命令。
在不需要事务的场景下,我们任然可以通过multi/exec来发送所有命令,来最小化通信次数和延迟。但是multi和exec也不是毫无损耗的,这些命令会延迟其他重要命令的执行。通过pipelining(不使用multi/exec)命令,我们可以获取同样的性能,同时不会有事务带来的性能损耗。
使用测试函数来测试函数的性能:
function benchmark_update_token(conn,duration){
for(function in (update_token,update_token_pipeline)){
count =0;
start = time.time();
end = start+duration;
while(time.time() < end){
count += 1;
function(conn,”token”,”user”,”item”);
}
delta = time.time() – start;
}
}5.性能考虑
使用redis-benchmark去测试redis性能,默认会限制50个客户端连接。
redis常用场景:
由于我们经常会在配置文件中维护一些标识性的信息,这就导致在每次修改标识后,所有的服务都需要去更新这个配置文件,同时还要重启服务来读取新的配置文件。 解决方案就是使用redis来替换配置文件的存取。 建议: 每个应用单独的部分都各自配置一个redis服务,如:保存日志的,保存静态资源的,保存cookies的等等。我们可以在一台机器上跑多个redis服务,只需要改变端口即可。这样会导致要连接的redis配置信息很多,由于每个服务都有各自的连接配置,导致很难管理。同理,单独运行一个redis服务来保存和维护所有的连接配置信息。
- 使用redis保存应用的配置信息