redis

Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。有字符串,链表,集 合和有序集合。支持在服务器端计算集合的并,交和补集(difference)等,还支持多种排序功能。所以Redis也可以被看成是一个数据结构服务 器。
Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”);也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。

 

 

 

 

第一种方法filesnapshotting:默认redis是会以快照的形式将数据持久化到磁盘的(一个二进 制文件,dump.rdb,这个文件名字可以指定),在配置文件中的格式是:save N M表示在N秒之内,redis至少发生M次修改则redis抓快照到磁盘。当然我们也可以手动执行save或者bgsave(异步)做快照。

工作原理简单介绍一下:当redis需要做持久化时,redis会fork一个子进程;子进程将数据写到磁盘上一个临时RDB文件中;当子进程完成写临时文件后,将原来的RDB替换掉,这样的好处就是可以copy-on-write

还有一种持久化方法是Append-only:filesnapshotting方法在redis异常死掉时, 最近的数据会丢失(丢失数据的多少视你save策略的配置),所以这是它最大的缺点,当业务量很大时,丢失的数据是很多的。Append-only方法可 以做到全部数据不丢失,但redis的性能就要差些。AOF就可以做到全程持久化,只需要在配置文件中开启(默认是no),appendonly yes开启AOF之后,redis每执行一个修改数据的命令,都会把它添加到aof文件中,当redis重启时,将会读取AOF文件进行“重放”以恢复到 redis关闭前的最后时刻。

LOG Rewriting随着修改数据的执行AOF文件会越来越大,其中很多内容记录某一个key的变化情况。因此redis有了一种比较有意思的特性:在后台重建AOF文件,而不会影响client端操作。在任何时候执行BGREWRITEAOF命令,都会把当前内存中最短序列的命令写到磁盘,这些命令可以完全构建当前的数据情况,而不会存在多余的变化情况(比如状态变化,计数器变化等),缩小的AOF文件的大小。所以当使用AOF时,redis推荐同时使用BGREWRITEAOF

AOF文件刷新的方式,有三种,参考配置参数appendfsyncappendfsync always每提交一个修改命令都调用fsync刷新到AOF文件,非常非常慢,但也非常安全;appendfsync everysec每秒钟都调用fsync刷新到AOF文件,很快,但可能会丢失一秒以内的数据;appendfsync no依靠OS进行刷新,redis不主动刷新AOF,这样最快,但安全性就差。默认并推荐每秒刷新,这样在速度和安全上都做到了兼顾。

可能由于系统原因导致了AOF损坏,redis无法再加载这个AOF,可以按照下面步骤来修复:首先做一个AOF文件的备份,复制到其他地方;修复原始AOF文件,执行:$ redis-check-aof –fix ;可以通过diff –u命令来查看修复前后文件不一致的地方;重启redis服务。

LOG Rewrite的工作原理:同样用到了copy-on-write:首先redis会fork一个子进程;子进程将最新的AOF写入一个临时文件;父进程 增量的把内存中的最新执行的修改写入(这时仍写入旧的AOF,rewrite如果失败也是安全的);当子进程完成rewrite临时文件后,父进程会收到 一个信号,并把之前内存中增量的修改写入临时文件末尾;这时redis将旧AOF文件重命名,临时文件重命名,开始向新的AOF中写入。

最后,为以防万一(机器坏掉或磁盘坏掉),记得定期把使用 filesnapshotting 或 Append-only 生成的*rdb *.aof文件备份到远程机器上。我是用crontab每半小时SCP一次。我没有使用redis的主从功能 ,因为半小时备份一次应该是可以了,而且我觉得有如果做主从有点浪费机器。这个最终还是看应用来定了。

 

 

========================

 

 

数据持久化通俗讲就是把数据保存到磁盘上,保证不会因为断电等因素丢失数据。

redis 需要经常将内存中的数据同步到磁盘来保证持久化。redis支持两种持久化方式,一种是 Snapshotting(快照)也是默认方式,另一种是Append-only file(缩写aof)的方式。先介绍下这两种dump方式再讲讲自己遇到的一些现象和想法,前面的内容是从网上整理出来的。

Snapshotting
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久 化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置

save 900 1  #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000

下面介绍详细的快照保存过程

1.redis调用fork,现在有了子进程和父进程。

2. 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数 据是fork时刻整个数据库的一个快照。

3.当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有 client的请求,这种方式会阻塞所有client请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式。下面介绍

Append-only file

aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是 appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要 通过fsync函数强制os写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)

appendonly yes              //启用aof持久化方式
# appendfsync always      //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec     //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no    //完全依赖os,性能最好,持久化没保证

aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件。具体过程如下

1. redis调用fork ,现在有父子两个进程
2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
3.父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
4.当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
5.现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

需要注意到是重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

运维上的想法

其实快照和aof一样,都使用了Copy-on-write技术。多次试验发现每次做数据dump的时候,内存都会扩大一倍(关于这个问题可以参考我去年写的redis的内存陷阱,很多人用redis做为缓存,数据量小,dump耗时非常短暂,所以不太容易发现),这个时候会有三种情况:

一:物理内存足以满足,这个时候dump非常快,性能最好

二:物理内存+虚拟内存可以满足,这个时候dump速度会比较慢,磁盘swap繁忙,服务性能也会下降。所幸的是经过一段比较长的时候数据dump完成了,然后内存恢复正常。这个情况系统稳定性差。

三: 物理内存+虚拟内存不能满足,这个时候dump一直死着,时间久了机器挂掉。这个情况就是灾难!

如果数据要做持久化又想保证稳定性,建议留空一半的物理内存。如果觉得无法接受还是有办法,下面讲:

快照和aof虽然都使用Copy-on-write,但有个不同点,快照你无法预测redis什么时候做dump,aof可以通过bgrewriteaof命令控制dump的时机。

根据这点我可以在一个服务器上开启多个redis节点(利用多CPU),使用aof的持久化方式。

例 如在24G内存的服务器上开启3个节点,每天用bgrewriteaof定期重新整理数据,每个节点dump的时间都不一样,这 样理论上每个节点可以消耗6G内存,一共使用18G内存,另外6G内存在单个节点dump时用到,内存一下多利用了6G! 当然节点开的越多内存的利用率也越高。如果带宽不是问题,节点数建议 = CPU数。

我的应用里为了保证高性能,数据没有做dump,也没有用aof。因为不做dump发生的故障远远低于做dump的时候,即使数据丢失了,自动修复脚本可以马上数据恢复。毕竟对海量数据redis只能做数据分片,那么落到每个节点上的数据量也不会很多。

redis的虚拟内存建议也不要用,用redis本来就是为了达到变态的性能,虚拟内存、aof看起来都有些鸡肋。

现在还离不开redis,因为它的mget是现在所有db里性能最好的,以前也考虑过用tokyocabinet hash方式做mget,性能不给力。直接用redis,基本上单个redis节点mget可以达到10W/s

纠错

之前说过redis做数据dump的时候内容会扩大一倍,后来我又做了些测试,发现有些地方说的不对。

top 命令并不是反映真实的内存占用情况,在top里尽管fork出来的子进程占了和父进程一样的内存,但是当做dump的时候没有写操作,实际使 用的是同一份内存的数据。当有写操作的时候内存才会真实的扩大(具体是不是真实的扩大一倍不确定,可能数据是按照页分片的),这才是真正的Copy- on-write。

基于这点在做数据持久化会更加灵活。

SAVE

SAVE 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘。

一般来说,在生产环境很少执行 SAVE 操作,因为它会阻塞所有客户端,保存数据库的任务通常由 BGSAVE 命令异步地执行。然而,如果负责保存数据的后台子进程不幸出现问题时, SAVE 可以作为保存数据的最后手段来使用。



http://www.runoob.com/redis/redis-hyperloglog.html

高并发核心技术 - 订单与库存

  • 问题:
    一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品。如何保证库存在高并发的场景下是安全的。
    1.不多发
    2.不少发
  • 下单涉及的一些步骤
    1.下单
    2.下单同时预占库存
    3.支付
    4.支付成功真正减扣库存
    5.取消订单
    6.回退预占库存

  • 什么时候进行预占库存
    方案一:加入购物车的时候去预占库存。
    方案二:下单的时候去预占库存。
    方案三:支付的时候去预占库存。
    分析:
    方案一:加入购物车并不代表用户一定会购买,如果这个时候开始预占库存,会导致想购买的无法加入购物车。而不想购买的人一直占用库存。显然这种做法是不可取的。
    方案二:商品加入购物车后,选择下单,这个时候去预占库存。用户选择去支付说明了,用户购买欲望是比 方案一 要强烈的。订单也有一个时效,例如半个小时。超过半个小时后,系统自动取消订单,回退预占库存。
    方案三:下单成功去支付的时候去预占库存。只有100个用户能支付成功,900个用户支付失败。用户体验不好,就像你走了一条光明大道,一路通畅,突然被告知此处不通行。而且支付流程也是一个比较复杂的流程,如果和减库存放在一起,将会变的更复杂。

所以综上所述:
选择方案二比较合理。

  • 重复下单问题

    1. 用户点击过快,重复提交两次。
    2. 网络延时,用户刷新或者点击下单重复提交。
    3. 网络框架重复请求,某些网络框架,在延时比较高的情况下会自动重复请求。
    4. 用户恶意行为。

解决办法

  1. 在UI拦截,点击后按钮置灰,不能继续点击,防止用户,连续点击造成的重复下单。
  2. 在下单前获取一个下单的唯一token,下单的时候需要这个token。后台系统校验这个 token是否有效,才继续进行下单操作。

 
  1. /**
  2. * 先生成 token 保存到 Redis
  3. * token 作为 key , 并设置过期时间 时间长度 根据任务需求
  4. * value 为数字 自增判断 是否使用过
  5. *
  6. * @param user
  7. * @return
  8. */
  9. public String createToken(User user) {
  10. String key = "placeOrder:token:" + user.getId();
  11. String token = UUID.randomUUID().toString();
  12. //保存到Redis
  13. redisService.set(key + token, 0, 1000L);
  14. return token;
  15. }
  16. /**
  17. * 校验下单的token是否有效
  18. * @param user
  19. * @param token
  20. * @return
  21. */
  22. public boolean checkToken(User user, String token) {
  23. String key = "placeOrder:token:" + user.getId();
  24. if (null != redisService.get(key + token)) {
  25. long times = redisService.increment(key + token, 1);
  26. if (times == 1) {
  27. //利用increment 原子性 判断是否 token 是否使用
  28. return true;
  29. } else {
  30. // 已经使用过了
  31. }
  32. //删除
  33. redisService.remove(key + token);
  34. }
  35. return false;
  36. }
  • 如何安全的减扣库存

同一个用户或者多个用户同时抢购一个商品的时候,我们如何做到并发安全减扣库存?

数据库操作商品库存:


 
  1. /**
  2. * Created by Administrator on 2017/9/8.
  3. */
  4. public interface ProductDao extends JpaRepository<Product, Integer> {

  5. /**
  6. * @param pid 商品ID
  7. * @param num 购买数量
  8. * @return
  9. */

  10. @Transactional
  11. @Modifying
  12. @Query("update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1")
  13. int reduceStock1(Integer pid, Integer num);

  14. /**
  15. * @param pid 商品ID
  16. * @param num 购买数量
  17. * @return
  18. */

  19. @Transactional
  20. @Modifying
  21. @Query("update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1 and availableNum - ?2 >= 0")
  22. int reduceStock2(Integer pid, Integer num);

  23. }

下单:


 
  1. /**
  2. * 下单操作1
  3. *
  4. * @param req
  5. */
  6. private int place(PlaceOrderReq req) {
  7. User user = userDao.findOne(req.getUserId());
  8. Product product = productDao.findOne(req.getProductId());
  9. //下单数量
  10. Integer num = req.getNum();
  11. //可用库存
  12. Integer availableNum = product.getAvailableNum();
  13. //可用预定
  14. if (availableNum >= num) {
  15. //减库存
  16. int count = productDao.reduceStock1(product.getId(), num);
  17. if (count == 1) {
  18. //生成订单
  19. createOrders(user, product, num);
  20. } else {
  21. logger.info("库存不足 3");
  22. }
  23. return 1;
  24. } else {
  25. logger.info("库存不足 4");
  26. return -1;
  27. }
  28. }
  29. /**
  30. * 下单操作2
  31. *
  32. * @param req
  33. */
  34. private int place2(PlaceOrderReq req) {
  35. User user = userDao.findOne(req.getUserId());
  36. Product product = productDao.findOne(req.getProductId());
  37. //下单数量
  38. Integer num = req.getNum();
  39. //可用库存
  40. Integer availableNum = product.getAvailableNum();
  41. //可用预定
  42. if (availableNum >= num) {
  43. //减库存
  44. int count = productDao.reduceStock2(product.getId(), num);
  45. if (count == 1) {
  46. //生成订单
  47. createOrders(user, product, num);
  48. } else {
  49. logger.info("库存不足 3");
  50. }
  51. return 1;
  52. } else {
  53. logger.info("库存不足 4");
  54. return -1;
  55. }
  56. }

方法1 :

不考虑库存安全的写法:


 
  1. /**
  2. * 方法 1
  3. * 减可用
  4. * 加预占
  5. * 库存数据不安全
  6. *
  7. * @param req
  8. */
  9. @Override
  10. @Transactional
  11. public void placeOrder(PlaceOrderReq req) {
  12. place1(req);
  13. }

分析:
在高并的场景下,假设库存只有 2 件 ,两个请求同时进来,抢购改商品,购买数量都是 2.
A请求 此时去获取库存,发现库存刚好足够,执行扣库存下单操作。
在 A 请求为完成的时候(事务未提交),B请求 此时也去获取库存,发现库存还有2. 此时也去执行扣库存,下单操作。

库存剩 2 ,但是卖出了 4 。最终数据库库存数量将变为 -2 ,所以库存是不安全的。

方法2 :

这个操作可以保证库存数据是安全的。


 
  1. /**
  2. * 方法 2
  3. * 减可用
  4. * 加预占
  5. * 库存数据不安全
  6. *
  7. * @param req
  8. */
  9. @Override
  10. @Transactional
  11. public void placeOrder(PlaceOrderReq req) {
  12. place2(req);
  13. }

分析: 在方法1 的基础上 ,更新库存的语句,增加了可用库存数量 大于 0, availableNum - num >= 0 ;实质是使用了数据库的乐观锁来控制库存安全,在并发量不是很大的情况下可以这么做。但是如果是秒杀,抢购,瞬时流量很高的话,压力会都到数据库,可能拖垮数据库。

方法3:

该方法也可以保证库存数量安全。


 
  1. /**
  2. * 方法 3
  3. * 采用 Redis 通一个时间 只能一个 请求修改 同一个商品的数量
  4. * <p>
  5. * 缺点并发不高,同时只能一个用户抢占操作,用户体验不好!
  6. *
  7. * @param req
  8. */
  9. @Override
  10. public void placeOrder2(PlaceOrderReq req) {
  11. String lockKey = "placeOrder:" + req.getProductId();
  12. boolean isLock = redisService.lock(lockKey);
  13. if (!isLock) {
  14. logger.info("系统繁忙稍后再试!");
  15. return 2;
  16. }

  17. //place2(req);
  18. place1(req);
  19. //这两个方法都可以
  20. redisService.unLock(lockKey);
  21. }

分析:

利用Redis 分布式锁, 强制控制 同一个商品,同时只能一个请求处理下单。 其他请求返回 ‘系统繁忙稍后再试!’;
强制把处理请求串行化,缺点并发不高 ,处理比较慢,不适合抢购等方案 。
用户体验也不好,明明看到库存是充足的,就是强不到。
相比方案2减轻了数据库的压力。

方法4 :

可以保证库存安全,满足高并发处理,但是相对复杂一点。


 
  1. /**
  2. * 方法 4
  3. * 商品的数量 等其他信息 先保存 Redis
  4. * 检查库存 减少库存 不是原子性, increment > 0 为准
  5. *
  6. * @param req
  7. */
  8. @Override
  9. public void placeOrder3(PlaceOrderReq req) {
  10. String key = "product:" + req.getProductId();
  11. // 先检查 库存是否充足
  12. Integer num = (Integer) redisService.get(key);
  13. if (num < req.getNum()) {
  14. logger.info("库存不足 1");
  15. }else{
  16. //不可在这里下单减库存,否则导致数据不安全, 情况类似 方法1
  17. }
  18. //减少库存
  19. Long value = redisService.increment(key, -req.getNum().longValue());
  20. //库存充足
  21. if (value >= 0) {
  22. logger.info("成功抢购 ! ");
  23. //TODO 真正减 库存 等操作 下单等操作 ,这些操作可用通过 MQ 其他方式
  24. place2(req);
  25. } else {
  26. //库存不足,需要增加刚刚减去的库存
  27. redisService.increment(key, req.getNum().longValue());
  28. logger.info("库存不足 2 ");
  29. }
  30. }

分析:利用Redis increment 的原子操作,保证库存安全。 事先需要把库存的数量等其他信息保存到Redis,并保证更新库存的时候,更新Redis。进来的时候 先 get 库存数量是否充足,再执行 increment。以 increment > 0 为准。检查库存 与 减少库存 不是原子性的。检查库存的时候技术库存充足也不可下单;否则造成库存不安全,原来类似 方法1.increment 是个原子操作,已这个为准。

redisService.increment(key, -req.getNum().longValue()) >= 0 说明库存充足,可以下单。

redisService.increment(key, -req.getNum().longValue()) < 0 的时候 不能下单,次数库存不足。并且需要 回加刚刚减去的库存数量,否则会导致刚才减扣的数量 一直买不出去。数据库与缓存的库存不一致。

次方法可以满足 高并抢购等一些方案,真正减扣库存和下单可以异步执行。

  • 订单时效问题,订单取消等为保证商家利益,同时把商品卖给有需要的人,订单下单成功后,往往会有个有效时间。超过这个时间,订单取消,库存回滚。

 public  static void placeOrder3() {        String pid="p001";        int pnum=2;        String watchkeys = "watchkeys";// 监视keys        Jedis jedis = new Jedis("183.131.229.193", 9901);            String key = "product:" + pid;        // 先检查 库存是否充足        String s=jedis.get(key);        Integer num = Integer.parseInt(s) ;        if (num < pnum) {           System.out.println("库存不足 1");        }else{            //不可在这里下单减库存,否则导致数据不安全, 情况类似 方法1;        }        //减少库存       // Long value = jedis.increment(key, -num);        Long value= jedis.incrBy(key, -pnum);        //库存充足        if (value >= 0) {           System.out.println("成功抢购 ! "+ "当前库存");            //TODO 真正减 扣 库存 等操作 下单等操作  ,这些操作可用通过 MQ 或 其他方式           // place2(req);        } else {            //库存不足,需要增加刚刚减去的库存            // jedis.increment(key, req.getNum().longValue());            System.out.println("库存不足  "+ "当前库存");        }    }

Watch命令

大家可能知道redis提供了基于incr命令来操作一个整数型数值的原子递增,那么我们假设如果redis没有这个incr命令,我们该怎么实现这个incr的操作呢?

那么我们下面的正主watch就要上场了。

如何使用watch命令

正常情况下我们想要对一个整形数值做修改是这么做的(伪代码实现):

      val = GET mykey
      val = val + 1
      SET mykey $val

但是上述的代码会出现一个问题,因为上面吧正常的一个incr(原子递增操作)分为了两部分,那么在多线程(分布式)环境中,这个操作就有可能不再具有原子性了。

研究过java的juc包的人应该都知道cas,那么redis也提供了这样的一个机制,就是利用watch命令来实现的。

watch命令描述

WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)

利用watch实现incr

具体做法如下:

      WATCH mykey
      val = GET mykey
      val = val + 1
      MULTI
      SET mykey $val EXEC

和此前代码不同的是,新代码在获取mykey的值之前先通过WATCH命令监控了该键,此后又将set命令包围在事务中,这样就可以有效的保证每个连接在执行EXEC之前,如果当前连接获取的mykey的值被其它连接的客户端修改,那么当前连接的EXEC命令将执行失败。这样调用者在判断返回值后就可以获悉val是否被重新设置成功。

注意点

由于WATCH命令的作用只是当被监控的键值被修改后阻止之后一个事务的执行,而不能保证其他客户端不修改这一键值,所以在一般的情况下我们需要在EXEC执行失败后重新执行整个函数。

执行EXEC命令后会取消对所有键的监控,如果不想执行事务中的命令也可以使用UNWATCH命令来取消监控。

实现一个hsetNX函数

我们实现的hsetNX这个功能是:仅当字段存在时才赋值

为了避免竞态条件我们使用watch事务来完成这一功能(伪代码):

    WATCH key  
    isFieldExists = HEXISTS key, field  
    if isFieldExists is 1  
    MULTI  
    HSET key, field, value EXEC else UNWATCH return isFieldExists

在代码中会判断要赋值的字段是否存在,如果字段不存在的话就不执行事务中的命令,但需要使用UNWATCH命令来保证下一个事务的执行不会受到影响。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值