redis集合运用小栗子分析
转自:http://www.tantengvip.com/2015/11/redis-set/#more-9098
Redis集合的应用:下单有礼功能实现
为了广大亲们能够好好剁手,我们也是拼了,“双十一”一过,我们就开始准备“双十二”了,大促活动有很多,本文以“下单有礼”的功能实现,讲讲redis的几个用法,如redis集合、自增的具体应用。这里仅仅举个小例子,在一个大型网站多个终端中,要考虑各种不同的情况和应用场景。
下单有礼的需求说明:
活动时间:12月11日10:00 ~ 12月14日09:59
活动内容:
1、活动期间,用户购买大促商品,首笔订单金额满68元及以上,即可获得一个68元优惠券礼包(每个用户只可参加1次)
2、优惠券礼包发放区分新老客
新客68元礼包包含优惠券:满30减5、满129减15、满188减20、满269减28
老客68元礼包包含优惠券:满99减5、满179减15、满269减20、满359减28
3、该活动只在app内,微信、M站不参加该活动
需求流程图:
一个小小的需求,要考虑的问题还是很多的:
- 发放礼包条件:首笔订单金额>=68元,活动期间一个用户只能领取一次大礼包
- 判定为马甲、疑似马甲用户不发放大礼包
- 优惠券礼包发放区分新老客(对应券包ID不同),新客是指注册未购买用户
移动端广告图片显示配置:
- 用户满足活动条件,并且赠送礼包成功,显示“领取礼包”图片
- 马甲、疑似马甲用户,显示“礼包已抢光”图片
- 礼包全部被领光,显示“礼包已抢光”图片
- 没有领取礼包的用户,显示活动图片
点击图片分享需要埋点。记录成功领取礼包用户UID,券包ID,订单金额、领取时间等信息。
基本思路:
- 从App端(ios,Android)下单成功且实付款大于68元的用户,存入redis hash并自增次数。
- 判断用户是否符合送礼包条件,并判断是否新老用户,新用户送新用户大礼包,老用户送老用户大礼包,并分别将用户ID放到不同的redis集合。
- 提供给移动端的接口,传入uid参数,返回如下用户状态:①新用户成功发放礼包 ②老用户成功发放礼包 ③马甲、疑似马甲 ④礼包已发完 5、其他(不满足赠送礼包条件的下单成功用户)
网站大量使用redis,为了解决高并发的性能压力,可以做数据缓存,并且有丰富的数据结构供不同场景选用,本功能用到的hash、集合的数据机构,以及自增等功能特性。
实现这个需求需要创建如下Redis key:
- com.xx.logic.ordergift_151212
因为要判断“首次”,所以需要用Redis计数,用hash结构,key为用户ID,value为满足条件次数。数据结构如图所示:
- com.xx.ordergift_151212_newuser
这是一个集合,存放首次满足条件并发放了新人礼包的新用户ID.
- com.xx.ordergift_151212_olduser
这是一个集合,存放首次满足条件并发放了老顾客礼包的用户ID.
- com.xx.ordergift_151212_fail
这是一个集合,存放礼包被抢光,或者送礼包接口异常的用户ID.
这几个集合主要是为了提供给移动端的API接口要返回下单成功后的用户状态,移动端需要在手机根据不同的用户状态显示不同的效果及其他处理。
(注:以下代码部分使用伪代码,包括Redis key,及方法命名调用都做了修改)
提供给移动端API接口调用的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* 判断下单完成用户状态
* Added by tuntun 2015.11.20
* @param int $uid 用户ID
* @return int 1.新用户成功发放礼包 2.老用户成功发放礼包 3.马甲、疑似马甲 4、礼包已发完 5、其他(不满足赠送礼包条件的下单成功用户)
*/
public function couponUserStatus($uid){
$is_majia = logic('majia')->isMajia($uid);
if(in_array($is_majia,array(1,3))) return 3;
$is_new = $this->couponUserIsNew($uid, true);
if($is_new) return 1;
$is_old = $this->couponUserIsNew($uid, false);
if($is_old) return 2;
$is_fail = $this->_redis->sismember($this->_payAlertKeys['order_coupon151212_fail'], $uid);
if($is_fail) return 4;
return 5;
}
|
以下代码是下单支付成功回调的接口中调用双十二活动下单有礼的方法,给满足条件的用户redis计数,判断新老用户发放不同的礼包,并存到对应的集合中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
/**
* 双12大促活动期间APP端下单首笔实付款满68元送大礼包
* 满足条件的下单成功的订单,用Redis计数。判断满足条件的新老用户发送对应礼包,马甲不发。
* Added by tuntun 2015.11.20
* @param int $uid 用户ID
* @param int $order_no 订单号
* @param float $real_payment 实付款
* @param int $pass_id 支付端来源 仅允许支付宝app,微信app,银联app, 微信好友代付
* @return bool
*/
public function coupon151212( $uid, $order_no, $real_payment, $pass_id ){
if( !$uid || !$order_no || $real_payment < 68 || !in_array( $pass_id, array(5,6,7,10,11) ) ){
Logger::info('Invalid Params!'.json_encode(func_get_args()), 'Coupon1212');
return false;
}
//App端支付宝走的是网页版,此种情况也算,查订单表确认来源
if( $pass_id == 5 ){
$pay_form = logic('pay')->getPayFrom( $order_no );
//判断是否来自xx android,xx iphone,xx android,xx iphone
if( !in_array( $pay_form, array(3,4,6,7) ) ){
Logger::info('Alipay do not from App. Payfrom:'.$pay_form, 'Coupon1212');
return false;
}
}
//判断活动时间是否有效
if ( $this->checkActivityStatus( 'coupon151212' ) ){
//满足实付款68元redis计数
$key = $this->_payAlertKeys['coupon151212'];
$this->_redis->hincrby( $key, $uid, 1 );
$this->_redis->expire( $key, $this->_pay_key_expire );
}else{
Logger::info('Activity time is over.', 'Coupon1212');
return false;
}
$hasCoupon = $this->hasCouponGift( $uid );//是否符合礼包赠送资格,判断满足68元条件次数是否是1
$coupon_fail= false;//领券失败标识
if($hasCoupon){
$user_type = User->checkUserType( $uid ); //1 新客 2老客
if(1 == $user_type){
$res = logic('Coupon')->reciveCouponbag( self::NEW_USER_COUPON_BAGID , $uid );
if($res['code'] == 1000){
Logger::info('Send newer coupon success. Uid:'.$uid, 'Coupon1212');
$this->_redis->sadd($this->_payAlertKeys['order_coupon_new'], $uid);
$this->_redis->expire($this->_payAlertKeys['order_coupon_new'] , $this->_pay_key_expire);
}else{
Logger::info('Send new coupon fail. Uid:'.$uid, 'Coupon1212');
$coupon_fail = true;
}
}elseif(2 == $user_type){
$res = logic('Coupon')->reciveCouponbag( self::OLD_USER_COUPON_BAGID , $uid );
if($res['code'] == 1000){
Logger::info('Send old Coupon success. Uid:'.$uid, 'Coupon1212');
$this->_redis->sadd($this->_payAlertKeys['order_coupon_old'], $uid);
$this->_redis->expire($this->_payAlertKeys['order_coupon_old'], $this->_pay_key_expire);
}else{
Logger::info('Send old coupon fail. Uid:'.$uid, 'Coupon1212');
$coupon_fail = true;
}
}
//领券失败放入集合
if($coupon_fail === true){
$this->_redis->sadd($this->_payAlertKeys['order_coupon_fail'], $uid);
$this->_redis->expire($this->_payAlertKeys['order_coupon_fail'] , $this->_pay_key_expire);
}
}else{
Logger::info('Can not get coupon gift. Uid:'.$uid);
//从可以发放礼包的新老用户集合中移除uid
$this->_redis->srem($this->_payAlertKeys['order_coupon_new'], $uid);
$this->_redis->srem($this->_payAlertKeys['order_coupon_old'], $uid);
}
return true;
}
|
判断是否有发放礼包资格,就是判断自增的redis hash的value值是否为1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 双十二活动判断用户是否有发送礼包资格
* @param int $uid 用户ID
* @return bool
*/
private function hasCouponGift($uid){
$key = $this->_payAlertKeys['order_coupon'];
$exists = $this->_redis->exists($key);
if($exists){
$value = $this->_redis->hget($key, $uid);
return $value == 1;
}
return false;
}
|
几个Redis的用法
以下是本功能用到的Redis方法:
hincrby,自增(或自减)一个hash结构数据的某个key的值(前提是值是整型)。
如:hincrby key test 1,如果key不存在就自动创建。字符串类型数字的自增可以用另一个方法:INCR.
hget,获取一个hash结果的key的某项值。
如上例中自增了一个key为test的值,用hget key test可以得到1.
sadd,把一个值放到一个集合中。
如sadd collection 3,4,5,6,7 ,就把这几个值放入collection这个集合中,sadd的集合都是不重复的。
sismember,判断某个值是否属于一个集合。
如上面放了几个数字到集合collection,使用sismember collection 3,返回1,表示3存在集合中。
集合的作用很强大,Redis可以方便的求集合的并集、差集、交集,如微博共同好友等功能的实现。
srem,从一个集合中移除某个值。
如srem collection 3,从集合中移除3.
exists,判断是否存在key。
expire,设置key过期时间。
具体使用方法请参考Redis手册