redis笔记3--事务及优

时间:2021-02-03 18:23:00

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。如图:

redis笔记3--事务及优

         商城的需求很简单:用户能展示商品和商品价格,当其他用户付钱时,卖家就会收到钱。为了记录足够的信息,我们要关联货品ID和卖货品的人,通过ZSET集合来记录该信息,score就是货品卖出的价钱。

redis笔记3--事务及优

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){
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;
}
}
           在一系列初始化工作之后,我们就告诉redis要监视库存inventory集合中的数据,验证用户是否还能卖这些货物,如果能卖,就将这些货物添加到超市,同时从库存表里删除这条信息。如果在监视期间,这条库存记录有改动,就会报错。

            如果user17要以97刀的价格卖itemM,需要执行的一系列操作如下:

  1. watch("inventory:17") 监视库存表
  2. redis笔记3--事务及优
  3. sismember("inventory:17","itemM"),查看商品是否还能卖
  4. zadd("market","item:17",97)  添加产品到超市
  5. redis笔记3--事务及优
  6. 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笔记3--事务及优

redis常用场景:

  1. 使用redis保存应用的配置信息
                  由于我们经常会在配置文件中维护一些标识性的信息,这就导致在每次修改标识后,所有的服务都需要去更新这个配置文件,同时还要重启服务来读取新的配置文件。            解决方案就是使用redis来替换配置文件的存取。            建议:           每个应用单独的部分都各自配置一个redis服务,如:保存日志的,保存静态资源的,保存cookies的等等。我们可以在一台机器上跑多个redis服务,只需要改变端口即可。这样会导致要连接的redis配置信息很多,由于每个服务都有各自的连接配置,导致很难管理。同理,单独运行一个redis服务来保存和维护所有的连接配置信息。