redis-02-实践

一、redis实现分布式锁

每个线程竞争同一把锁,且只有同一线程能删除它上的锁,其它线程无法删除

1、获取锁方法

用setnx命令设置(并设置失效时间,防止死锁),若写入成功产生随机值并将这个随机值存到threadLocal中,释放锁时比较redis中存放的值是否和threadLocal存放的值相等,判断只有加锁的线程才能释放锁

//存放每个线程获取锁的值,释放锁时比较当前线程锁值是否等于获取锁key的值 
private ThreadLocal<String> local = new ThreadLocal<>();
@Override
//阻塞式的加锁
public void lock() {
	//1.尝试加锁
	if(tryLock()){
		return;
	}
	//2.加锁失败,当前任务休眠一段时间
	try {
		Thread.sleep(10);//性能浪费
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	//3.递归调用,再次去抢锁
	lock();
}
@Override
//阻塞式加锁,使用setNx命令返回OK的加锁成功,并生产随机值
public boolean tryLock() {
	//产生随机值,标识本次锁编号
	String uuid = UUID.randomUUID().toString();
	Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();

	/**
	 * key:我们使用key来当锁
	 * uuid:唯一标识,这个锁是我加的,属于我
	 * NX:设入模式【SET_IF_NOT_EXIST】--仅当key不存在时,本语句的值才设入
	 * PX:给key加有效期
	 * 1000:有效时间为 1 秒
	 */
	String ret = jedis.set(KEY, uuid,"NX","PX",1000);

	//设值成功--抢到了锁
	if("OK".equals(ret)){
		local.set(uuid);//抢锁成功,把锁标识号记录入本线程--- Threadlocal
		return true;
	}

	//key值里面有了,我的uuid未能设入进去,抢锁失败
	return false;
}

2、释放锁(只有获取锁的线程才能释放它加的锁)

//正确解锁方式
public void unlock() {
	//读取lua脚本
	String script = FileUtils.getScript("unlock.lua");
	//获取redis的原始连接
	Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();
	//通过原始连接连接redis执行lua脚本
	jedis.eval(script, Arrays.asList(KEY), Arrays.asList(local.get()));
}

unlokc,lua脚本(保证执行操作原子性)
判断redis.get(key)获取锁值是否等于当前线程threadLocla中的值,如果是同一个线程加的锁,那么可以删除

if redis.call("get",KEYS[1]) == ARGV[1] then 
    return redis.call("del",KEYS[1]) 
else 
    return 0 
end

二、HASH存储数据(购物车实现)

购物车key: 购物车模版名+商品编号(cart:用户id)
购物车value: 商口编号
实现:
1、向我的购物车中加放商品 001 2件, 002 2件, 003 3件
指令:hmset card:001 prodId001 2 prodId002 3 prodId003 3
2、获取我的购物车中的商品
指令:hgetall cart:001
3、商品001加一件
指令:HINCRBY card:001 prodId001 1
4、购物车中总共有多少商品
指令:查看购物车长度 hlen card:001

三、LIST存储数据(队列和栈)

我的微信公众号关注了CSDN,那么打开订阅号获取CSDN推送的消息
key:mes+ userId
value: 消息id
CSDN发布一条消息:
指令:lpush mes:004 999
进入订阅号后查看推送的消息
指令:lrange mes:004 0 5

四、SET集合

1、同一个人只能投一次票
给用户user001投票
sadd user:001 user:002 user:003 user:004
2、一个活动很多人参与抽奖,一个人只能参与一次
指令1:活动中加入参与抽奖的用户Ideas:sadd active:001 user:001 user:002 user:003
指令2:在活动1中抽奖2个人:SRANDMEMBER active:001 2
缺点:每次抽到人会重复,抽到奖的人应该移除集合
改进:spop active:002 2,抽完奖移除去
3、朋友圈发送的消息点赞
key: 被点赞的消息id
value:点赞的用户id
如果需要有序,可用zset存储
点赞:zadd zan:001 user:001 user:002 user:003
取消点赞:zrem zan001 user:001
检查是否点过赞:sismember zan:001 userId:001
查看其它人点赞信息:
只有相互是好朋的用户才能看到其它人的点赞,取好友交集
user:001 好友 user:002 user:003 user:004
user:002 好友 user:002 user:004 user:005 user:006
那么user002只能看到user:002 user:004 的点赞

4、微博的微关系设计
在这里插入图片描述
条件1)user001关注的人
sadd user001Cares user002,user003,user004,user005
条件2) user003关注的人
sadd user003Cares user005,user007,user003,user9
条件3) user004关注的人
sadd user004Cares user009,user003,user010
计算1)user001和user003共同关注的人
sinter user001Cares user003Cares , 计算结果为
计算2) 我关注的人也关注他(user003)
sismember user001Cares user003
sismember user003Cares user004
计算3)我可能认识的人
SDIFF user001Cares user003Cares

五、ZSET有序集合

1、排行榜
比如腾讯新闻每天热点新闻根据阅读数排行
1)key:topic+日期 (topic:20191022)
2)value:点击量
3)点击话题(阅读加1):zincrby topic:20191022 1
4)展示今日前9排名:zrevrange topic:20191022 0 20 withscores
5)统计近3天点击数据:zunionstore topic:3day 3 topic:20191022 topic:20191021 topic:20191020

六、文章投票实例

1、 发布文章
hash结构存储文章
key:article+文章Id
value :文章属性及内容
1)功能:发布文章,记录文章初始投票分值,记录文章用户投票

//用hash记录文章
String articleId = String.valueOf(jedis.incr("article:")); 
String article = "article:" + articleId;
long now = System.currentTimeMillis() / 1000;
HashMap<String,String> articleData = new HashMap<String,String>();
articleData.put("title", title);
articleData.put("link", link);
articleData.put("user", userId);
articleData.put("now", String.valueOf(now));
articleData.put("votes", "1");
jedis.hmset(article, articleData);
//给文章投票且1个星期内有效,用set集合存储,投票不可重复
//投票键: voted:
String voted = "voted:" + articleId;
jedis.sadd(voted, userId);
jedis.expire(voted, Constants.ONE_WEEK_IN_SECONDS);
//记录文章初始分值(zset集合,后序按分值排序)
jedis.zadd("score:info", now + Constants.VOTE_SCORE, article);
//记录文章发布时间(zset集合,后序按文章发布时间排序)
jedis.zadd("time:", now, article);

2)功能:文章投票,七天内可投票,投票不可重复,投票后增加分值

//计算投票截止时间
long cutoff = (System.currentTimeMillis() / 1000) - Constants.ONE_WEEK_IN_SECONDS;
//检查是否还可以对文章进行投票,如果该文章的发布时间比截止时间小,则已过期,不能进行投票
if (jedis.zscore("time:", article) < cutoff){
    return;
}
//获取文章主键id
String articleId = article.substring(article.indexOf(':') + 1); 
//没有投过票才投票
if (jedis.sadd("voted:" + articleId, userId) == 1) {
     //增加分值
    jedis.zincrby("score:info", Constants.VOTE_SCORE, article);
    //投票数加1
    jedis.hincrBy(article, "votes", 1L);
}

七、抢红包实例

1、生成子红包

付款后,根据所选红包个数,和付款金额生成对应个数子红包、金额、红包id(唯一)属性放到redis的list结构中,
红包key: hongbao+随机数+用户id,用于标识这个红包是某个人在某时间发送的
value: 对应子红包

//将生成子红包放到redis的list中
object.put("id", "rid_"+num); //红包ID
object.put("money", money);   //红包金额
jedis.lpush("hongBaoPoolKey"+random+userId, object.toJSONString());

2、抢红包

循环抢红包直到红包被抢完才退出,如果是同一个人抢过那么返回,抢成功生成抢红包明细记录

//while循环中执行,如果出现并发抢失败,那么继续抢,直到红包被抢完退出循环
while(true){
//子红包List key
String hongBaoKey = Basic.hongBaoPoolKey+random+userId;
//用户抢红包明细List key
String hongBaoDetailKey = Basic.hongBaoDetailListKey + hongBaoKey;
//已抢过红包的set key
String userIdRecordKey = Basic.userIdRecordKey + hongBaokey;
	//抢红包  eval 调用LUA脚本里的业务代码返回结果:  object ={红包id, 红包金额, 用户Id},eval参数:lua脚本,lua脚本接收参数个数,红包key,记录抢红包用户明细Key,已抢过红包的用户集合Key,抢红包的用户ID
	Object object = jedis.eval(Basic.getHongBaoScript,4,hongBaoKey ,hongBaoDetailKey ,userIdRecordKey ,userId);
	if(null != object){
		System.out.println("用户ID号:"+userid+" 抢到红包的详情是 "+object);
	}else{
	    //如果抢失败,检测是否是因为红包被抢完,如果抢完那么退出
		if(jedis.llen(Basic.hongBaoPoolKey) == 0){
			break;
		}
	}
}

Lua脚本:

KEYS[1]:红包key,
KEYS[2]:记录抢红包用户明细Key,
KEYS[3]:已抢过红包的用户HashKey,
KEYS[4]:抢红包的用户ID
判断用户是否抢过红包,用户id是否在hash Field中存在,如果抢过,返回;没有抢过,从list中弹出一个元素;弹出成功,生成抢红包明细数据;将已抢红包的用户Id 写入hash中 field分别是每个userId; 将生成红包明细放到抢红包用户List 中

public static String getHongBaoScript =
		            "if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then\n"   + 
		                 "return nil\n" + 
		            "else\n"  +
		                  "local hongBao = redis.call('rpop', KEYS[1]);\n"  +
		            	  "if hongBao then\n"  +
			            	 "local x = cjson.decode(hongBao);\n"  +
			            	 "x['userId'] = KEYS[4];\n"  +
			            	 "local re = cjson.encode(x);\n"  +
			            	 "redis.call('hset', KEYS[3], KEYS[4], '1');\n"  +
			            	 "redis.call('lpush', KEYS[2], re);\n" + 
			            	 "return re;\n"  +
		                  "end\n"  +
		            "end\n"  +
		            "return nil";  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值