set命令使用
redis set 和java的set集合功能差不多的
- 集合(Set) 的主要功能就是求并集、交集、差集。
A = {'a', 'b', 'c'}
B = {'a', 'e', 'i', 'o', 'u'}
inter(x, y): 交集,在集合x和集合y中都存在的元素。
inter(A, B) = {'a'}
union(x, y): 并集,在集合x中或集合y中的元素,如果一个元素在x和y中都出现,那只记录一次即可。
union(A,B) = {'a', 'b', 'c', 'e', 'i', 'o', 'u'}
diff(x, y): 差集,在集合x中而不在集合y中的元素。
diff(A,B) = {'b', 'c'}
card(x): 基数,一个集合中元素的数量。
card(A) = 3
空集: 基数为0的集合。
- sadd(key, member)
向名称为key的set中添加元素member - smembers(key)
返回名称为key的set的所有元素
127.0.0.1:6379> sadd users u1
(integer) 1
127.0.0.1:6379> sadd users u2
(integer) 1
127.0.0.1:6379> sadd users u3 u4
(integer) 2
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u1"
4) "u3"
- srem(key, member) :
删除名称为key的set中的元素member
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u1"
4) "u3"
127.0.0.1:6379> srem users u1
(integer) 1
127.0.0.1:6379> srem users u3 u4
(integer) 2
127.0.0.1:6379> smembers users
1) "u2"
- sismember(key, member) :
member是否是名称为key的set的元素
127.0.0.1:6379> smembers users
1) "u2"
127.0.0.1:6379> sismember users u2
(integer) 1
127.0.0.1:6379> sismember users u1
(integer) 0
- scard(key) :
返回名称为key的set的基数,一个集合中元素的数量。
127.0.0.1:6379> smembers users
1) "u2"
127.0.0.1:6379> scard users
(integer) 1
- smove(srckey, dstkey, member) :
将member元素从source集合移动到destination集合。
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u1"
4) "u3"
127.0.0.1:6379> smembers blacklist
(empty list or set)
127.0.0.1:6379> smove users blacklist u1
(integer) 1
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u3"
127.0.0.1:6379> smembers blacklist
1) "u1"
- srandmember(key) :
随机返回名称为key的set的一个元素
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u3"
127.0.0.1:6379> srandmember users
"u3"
127.0.0.1:6379> srandmember users 2
1) "u2"
2) "u3"
- spop(key) :
随机返回并删除名称为key的set中一个元素
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u3"
127.0.0.1:6379> spop users
"u3"
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
- sinter(key1, key2,…key N) :
求交集。
127.0.0.1:6379> smembers group1
1) "3"
2) "2"
3) "4"
4) "1"
5) "a"
127.0.0.1:6379> smembers group2
1) "b"
2) "a"
3) "1"
4) "c"
127.0.0.1:6379> sinter group1 group2
1) "a"
2) "1"
- sinterstore(dstkey, (key2,…key N)) :
求交集并将交集保存到dstkey的集合
127.0.0.1:6379> sinterstore group3 group1 group2
(integer) 2
127.0.0.1:6379> smembers group1
1) "3"
2) "4"
3) "1"
4) "2"
5) "a"
127.0.0.1:6379> smembers group2
1) "b"
2) "a"
3) "1"
4) "c"
127.0.0.1:6379> smembers group3
1) "a"
2) "1"
- sunion(key1, (keys)) :
求并集
127.0.0.1:6379> sunion group1 group2
1) "3"
2) "4"
3) "1"
4) "2"
5) "b"
6) "a"
7) "c"
- sunionstore(dstkey, (keys)) :
求并集并将并集保存到dstkey的集合
127.0.0.1:6379> sunionstore group4 group1 group2
(integer) 7
127.0.0.1:6379> smembers group4
1) "3"
2) "4"
3) "1"
4) "2"
5) "b"
6) "a"
7) "c"
- sdiff(key1, (keys)) :
求差集
127.0.0.1:6379> smembers group1
1) "3"
2) "4"
3) "1"
4) "2"
5) "a"
127.0.0.1:6379> smembers group2
1) "b"
2) "a"
3) "1"
4) "c"
127.0.0.1:6379> sdiff group1 group2
1) "2"
2) "3"
3) "4"
- diffstore(dstkey, (keys)) :
求差集并将差集保存到dstkey的集合
127.0.0.1:6379> sdiffstore group5 group1 group2
(integer) 3
127.0.0.1:6379> smembers group5
1) "2"
2) "3"
3) "4"
淘宝黑名单
一、黑名单过滤器业务场景分析
淘宝的商品评价功能,不是任何人就能评价的,有一种职业就是差评师,差评师就是勒索敲诈商家,
这种差评师在淘宝里面就被设置了黑名单,即使购买了商品,也评价不了。
二 、解决的技术方案
黑名单过滤器除了针对上文说的淘宝评价,针对用户黑名单外,其实还有ip黑名单、设备黑名单等。
在高并发的情况下,通过数据库过滤明显不符合要求,一般的做法都是通过Redis来实现的。
那redis那种数据结构适合做这种黑名单的呢?
答案是:set
步骤1:先把数据库的数据同步到redis的set集合中。
步骤2:评价的时候验证是否为黑名单,通过sismember命令来实现。
三、SpringBoot+redis模仿实现校验器
步骤1:提前先把数据刷新到redis缓存中。
/**
* 提前先把数据刷新到redis缓存中
*/
@PostConstruct
public void init(){
log.info("启动初始化 ..........");
List<Integer> blacklist=this.blacklist();
//this.redisTemplate.delete(Constants.BLACKLIST_KEY);
//TODO:异步插入数据库DB
blacklist.forEach(t->this.redisTemplate.opsForSet().add(Constants.BLACKLIST_KEY,t));
}
/**
* 模拟100个黑名单
*/
public List<Integer> blacklist() {
List<Integer> list=new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i);
}
return list;
}
步骤2:编写黑名单校验器接口
/**
*编写黑名单校验器接口
* true=黑名单
* false=不是黑名单
*/
@GetMapping(value = "/isBlacklist")
public boolean isBlacklist(Integer userId) {
boolean bo=false;
try {
//到set集合中去校验是否黑名单,
bo = this.redisTemplate.opsForSet().isMember(Constants.BLACKLIST_KEY,userId);
log.info("查询结果:{}", bo);
} catch (Exception ex) {
//这里的异常,一般是redis瘫痪 ,或 redis网络timeout
log.error("exception:", ex);
//TODO 走DB查询
}
return bo;
}
京东京豆抽奖
一、京东京豆抽奖的业务场景分析
- 可以无线抽,奖品是无限的,不同奖品的概率是不一样的;
二、京东京豆抽奖的技术方案
京豆抽奖一般是采用redis的set集合来操作的,那为什么是set集合适用于抽奖呢?
2个原因:
1.set集合的特点是元素不重复 存放1个 5个 10个京豆 谢谢参与
2.set集合支持随机读取
具体的技术方案是采用set集合的srandmember命令来实现,随机返回set的一个元素
三、SpringBoot+Redis 实现京东京豆抽奖
步骤1:奖品的初始化
由于set集合是不重复,故在奖品初始化的时候,要为每个奖品设置一个序列号。
/**
*提前先把数据刷新到redis缓存中。
*/
@PostConstruct
public void init(){
log.info("启动初始化..........");
boolean bo=this.redisTemplate.hasKey(Constants.PRIZE_KEY);
if(!bo){
List<String> crowds=this.prize();
crowds.forEach(t->this.redisTemplate.opsForSet().add(Constants.PRIZE_KEY,t));
}
}
/**
*按一定的概率初始化奖品
*/
public List<String> prize() {
List<String> list=new ArrayList<>();
//10个京豆,概率10%
for (int i = 0; i < 10; i++) {
list.add("10-"+i);
}
//5个京豆,概率20%
for (int i = 0; i < 20; i++) {
list.add("5-"+i);
}
//1个京豆,概率60%
for (int i = 0; i < 60; i++) {
list.add("1-"+i);
}
//0个京豆,概率10%
for (int i = 0; i < 10; i++) {
list.add("0-"+i);
}
return list;
}
步骤2:抽奖
@GetMapping(value = "/prize")
public String prize() {
String result="";
try {
//随机取1次。
String object = (String)this.redisTemplate.opsForSet().randomMember(Constants.PRIZE_KEY);
if (!StringUtils.isEmpty(object)){
//截取序列号 例如10-1
int temp=object.indexOf('-');
int no=Integer.valueOf(object.substring(0,temp));
switch (no){
case 0:
result="谢谢参与";
break;
case 1:
result="获得1个京豆";
break;
case 5:
result="获得5个京豆";
break;
case 10:
result="获得10个京豆";
break;
default:
result="谢谢参与";
}
}
log.info("查询结果:{}", object);
} catch (Exception ex) {
log.error("exception:", ex);
}
return result;
}
支付宝抽奖
二、支付宝抽奖的技术方案
思考一个问题:支付宝的抽奖 和 京东京豆的抽奖有什么区别????
- 京豆抽奖:奖品是可以重复,例如抽5京豆可以再抽到5京豆,即京豆是无限量抽。
- 支付宝抽奖: 奖品不能重复抽,例如1万人抽1台华为手机;再给大家举一个熟悉的例子:
例如公司年会,抽中奖品的人,下一轮就不能重复抽取,不然就会重复中奖。
- 技术方案和京东的京豆类似,但是不同的是
京东的京豆用了srandmember命令,即随机返回set的一个元素 - 支付宝的抽奖要用spop命令,即随机返回并删除set中一个元素
为什么呢? - 因为支付宝的奖品有限,不能重复抽,故抽奖完后,必须从集合中剔除中奖的人。
再举个每个人都参与过的例子,年会抽奖,你公司1000人,年会抽奖3等奖500名100元,2等奖50名1000元,1等奖10名10000元,
在抽奖的设计中就必须把已中奖的人剔除,不然就会出现重复中奖的概率。
SpringBoot+Redis 实现支付宝抽奖
步骤1:初始化抽奖数据
/**
*提前先把数据刷新到redis缓存中。
*/
@PostConstruct
public void init(){
log.info("启动初始化..........");
boolean bo=this.redisTemplate.hasKey(Constants.PRIZE_KEY);
if(!bo){
List<Integer> crowds=this.prize();
crowds.forEach(t->this.redisTemplate.opsForSet().add(Constants.PRIZE_KEY,t));
}
}
/**
* 模拟10个用户来抽奖 list存放的是用户id
* 例如支付宝参与抽奖,就把用户id加入set集合中
* 例如公司抽奖,把公司所有的员工,工号都加入到set集合中
*/
public List<Integer> prize() {
List<Integer> list=new ArrayList<>();
for(int i=1;i<=10;i++){
list.add(i);
}
return list;
}
步骤2:抽奖逻辑
@GetMapping(value = "/prize")
public List<Integer> prize(int num) {
try {
SetOperations<String, Integer> setOperations= this.redisTemplate.opsForSet();
//spop命令,即随机返回并删除set中一个元素
List<Integer> objs = setOperations.pop(Constants.PRIZE_KEY,num);
log.info("查询结果:{}", objs);
return objs;
} catch (Exception ex) {
log.error("exception:", ex);
}
return null;
}
微博榜单与QQ群的随机展示
一、随机展示业务场景分析
思考题:为什么要随机展示?
因为展示的区域有限啊,在那么小的地方展示全部数据是不可能的,通常的做法就是随机展示一批数据,然后用户点击“换一换”按钮,再随机展示另一批。
二、随机展示的redis技术方案
随机展示的原因就是区域有限,而区域有限的地方通常就是首页或频道页,这些位置通常都是访问量并发量非常高的,
一般是不可能采用数据库来实现的,通常都是Redis来实现。
redis的实现技术方案:
步骤1:先把数据准备好,把所有需要展示的内容存入redis的Set数据结构中。
步骤2:通过srandmember命令随机拿一批数据出来。
三、 SpringBoot+Redis 实现高并发随机展示
步骤1:提前先把数据刷新到redis缓存中
/**
*提前先把数据刷新到redis缓存中。
*/
@PostConstruct
public void init(){
log.info("启动初始化 群..........");
List<String> crowds=this.crowd();
this.redisTemplate.delete(Constants.CROWD_KEY);
crowds.forEach(t->this.redisTemplate.opsForSet().add(Constants.CROWD_KEY,t));
}
/**
* 模拟100个热门群,用于推荐
*/
public List<String> crowd() {
List<String> list=new ArrayList<>();
for (int i = 0; i < 100; i++) {
Random rand = new Random();
int id= rand.nextInt(10000);
list.add("群"+id);
}
return list;
}
步骤2:编写随机查询接口
@GetMapping(value = "/crowd")
public List<String> crowd() {
List<String> list=null;
try {
//采用redis set数据结构,随机取出10条数据
list = this.redisTemplate.opsForSet().randomMembers(Constants.CROWD_KEY,10);
log.info("查询结果:{}", list);
} catch (Exception ex) {
//这里的异常,一般是redis瘫痪 ,或 redis网络timeout
log.error("exception:", ex);
//TODO 走DB查询
}
return list;
}
微博榜单的随机展示
业务场景分析
他这个换一换,可能包含了不同类型的数据,例如主播榜,美食榜等等、每个榜单里面包含三条数据;
SpringBoot+redis实现微博榜单随机展示
步骤1、创建实体类WeiBoRecommendList
@Data
@ToString
public class WeiBoRecommendList {
public int id;
public String name;
public List<String> user;
}
步骤二、编写初始化数据到Redis中
public final String COMMEND_KEY="recommend";
public Object init() {
ArrayList<WeiBoRecommendList> list = new ArrayList<>();
for (int i=0; i<20; i++){
WeiBoRecommendList item= new WeiBoRecommendList();
item.setId(i);
item.setName("榜单-"+i);
Random random=new Random(1);
ArrayList<String> users = new ArrayList<>();
for (int index=0; index<3 ; index++){
int userId = random.nextInt(10000);
users.add("用户-"+userId);
}
item.setUser(users);
list.add(item);
}
list.forEach( t -> redisTemplate.opsForSet().add(COMMEND_KEY,t) );
return "init ok";
}
步骤三、编写随机展示接口
@Api("微博推荐榜单")
@RestController
@RequestMapping("/wb")
public class WbRecommendController {
@Autowired
WbRecommendService service;
@PostMapping("/init")
public Object init(){
return service.init();
}
@PostMapping("/random")
public Object random(Integer num) {
Object random = service.random( num);
return random;
}
@PostMapping("/getOne")
public Object getOne() {
Object random = service.getOne();
return random;
}
}
@Service
public class WbRecommendService {
@Autowired
RedisTemplate redisTemplate;
public final String COMMEND_KEY="recommend";
public Object init() {
ArrayList<WeiBoRecommendList> list = new ArrayList<>();
for (int i=0; i<20; i++){
WeiBoRecommendList item= new WeiBoRecommendList();
item.setId(i);
item.setName("榜单-"+i);
Random random=new Random(1);
ArrayList<String> users = new ArrayList<>();
for (int index=0; index<3 ; index++){
int userId = random.nextInt(10000);
users.add("用户-"+userId);
}
item.setUser(users);
list.add(item);
}
list.forEach( t -> redisTemplate.opsForSet().add(COMMEND_KEY,t) );
return "init ok";
}
public Object random(Integer num) {
List list = redisTemplate.opsForSet().randomMembers(COMMEND_KEY, num);
return list;
}
public Object getOne() {
WeiBoRecommendList member = (WeiBoRecommendList) redisTemplate.opsForSet().randomMember(COMMEND_KEY);
if (member != null){
return member;
}else {
//TODO -->查询DB
return null;
}
}
}
帖子点赞
一、微博点赞业务场景分析
梳理点赞的业务场景,它有2个接口:
第一个:点赞或取消点赞,用户点击功能
第二个接口:查看帖子信息:通过用户id 和帖子id,查看该帖子的点赞数、该用户是否点赞状态。
二、微博点赞的技术方案
点赞的关键技术就是要判断该用户是否点赞,已重复点赞的不允许再点赞,即过滤重复,虽然业务不复杂,可以采用数据库直接实现,
但是对于微博这种高并发的场景,不可能查数据库的,一般是缓存,即redis
第一个:点赞或取消点赞,用户点击功能采用的是redis的set数据结构,key=like:postid value={userid}
- 采用sadd命令,添加点赞
127.0.0.1:6379> sadd like:1000 101
(integer) 1
127.0.0.1:6379> sadd like:1000 102
(integer) 1
127.0.0.1:6379> sadd like:1000 103
(integer) 1
127.0.0.1:6379> smembers like:1000
1) "101"
2) "102"
3) "103"
- 采用srem命令,取消点赞
127.0.0.1:6379> srem like:1000 101
(integer) 1
127.0.0.1:6379> smembers like:1000
1) "102"
2) "103"
- 第二个接口:查看帖子信息:通过用户id 和帖子id,查看该帖子的点赞数、该用户是否点赞状态。
采用scard命令,点赞总数
127.0.0.1:6379> smembers like:1000
1) "102"
2) "103"
127.0.0.1:6379> scard like:1000
(integer) 2
- 采用sismember命令,判断是否点赞
127.0.0.1:6379> smembers like:1000
1) "102"
2) "103"
127.0.0.1:6379> sismember like:1000 102
(integer) 1
127.0.0.1:6379> sismember like:1000 101
(integer) 0
三、SpringBoot+Redis 实现微博点赞
步骤1:点赞逻辑
@Service
public class LikeService {
@Autowired
RedisTemplate redisTemplate;
public final String postKey="post::like::";
@Transactional
public Boolean postLike(Integer userId, Integer postId) {
String key=postKey+postId;
//TODO 异步插入数据库表;
Boolean flag = redisTemplate.opsForSet().isMember(key, userId);
if (!flag){
redisTemplate.opsForSet().add(key,userId);
return true;
}else {
return false;
}
}
@Transactional
public Long postUnlike(Integer userId, Integer postId) {
//TODO 异步删除数据表点赞记录;
String key=postKey+postId;
Long flag = redisTemplate.opsForSet().remove(key, userId);
return flag;
}
@Transactional
public Map getPost(Integer postId, Integer userId) {
String key=postKey+postId;
// 获取帖子的内容 ==> Map map=redisTemplate.opsForHash().entries(key)
//这里偏向Set的使用
// 获取微博的阅读量==> Long readNum= redisTemplate.opsForValue().get(key)
//点赞数
Long likeSize = redisTemplate.opsForSet().size(key);
//用户是否点赞
Boolean isLike = redisTemplate.opsForSet().isMember(key, userId);
HashMap<String, Object> post = new HashMap<>();
post.put("isLike",isLike);
post.put("likeSize",likeSize);
//post.put("title",map.get("title");
//post.put("content",map.get("content");
//post.put("readNum",readNum);
return post;
}
}
//接口类
@Api("用户点赞")
@RestController
@RequestMapping("/like")
public class LikeController {
@Autowired
LikeService likeService;
@PostMapping("/postLike")
public Object postLike(Integer userId , Integer postId){
Boolean f = likeService.postLike(userId, postId);
if (f){
return "post OK";
}else {
return "已经点赞了,无法重复点了";
}
}
@PostMapping("/postUnlike")
public Object postUnlike(Integer userId , Integer postId){
Long f = likeService.postUnlike(userId, postId);
if (f > 0){
return "unlike OK";
}else {
return "你还没有点赞或者重复点赞";
}
}
@PostMapping("/getPost")
public Object getPost(Integer postId,Integer userId){
Map f = likeService.getPost(postId,userId);
if (f!=null){
return f;
}else {
//TODO :去DB查询,查询后插入redis;
// likeService.getPostFromDB(postId,userId);
return null;
}
}
}
关注与粉丝
一、微博关注与粉丝的业务场景分析
我关注了雷军:我就是雷军的粉丝follower;
雷军被阿甘关注:雷军就是阿甘的关注followee;
二、微博关注与粉丝的redis技术方案
技术方案:每个用户都有2个集合:关注集合和粉丝集合
例如 我关注了雷军,做了2个动作
1.把我的userid加入雷军的粉丝follower集合set
2.把雷军的userid加入我的关注followee集合set
集合key设计
我的关注集合 key=followee:我的userid
雷军的粉丝集合 key=follower:雷军userid
SpringBoot+Redis 实现微博关注与粉丝
功能1:关注
/**
* 我关注了雷军,我即使雷军的粉丝 follower
* userId=我
* followeeId=雷军
*/
@ApiOperation(value="关注")
@PostMapping(value = "/follow")
public void follow(Integer userId,Integer followeeId) {
relationService.follow(userId,followeeId);
}
/**
* 我关注了雷军
*/
public void follow(Integer userId,Integer followeeId){
SetOperations<String, Integer> opsForSet = redisTemplate.opsForSet();
//我的关注集合
String followeekey = Constants.CACHE_KEY_FOLLOWEE + userId;
//把雷军的followeeId,加入我的关注集合中
opsForSet.add(followeekey,followeeId);
//TODO:异步插入数据库DB;
//雷军的粉丝集合
String followerkey=Constants.CACHE_KEY_FOLLOWER+followeeId;
//把我的userid加入雷军的粉丝follower集合set
opsForSet.add(followerkey,userId);
//TODO:异步插入数据库DB;
}
功能2:我的关注
@ApiOperation(value="查看我的关注")
@GetMapping(value = "/myFollowee")
public List<UserVO> myFollowee(Integer userId){
return this.relationService.myFollowee(userId);
}
/**
* 查看我的关注
*/
public List<UserVO> myFollowee(Integer userId){
SetOperations<String, Integer> opsForSet = redisTemplate.opsForSet();
//关注集合
String followeekey = Constants.CACHE_KEY_FOLLOWEE + userId;
Set<Integer> sets= opsForSet.members(followeekey);
//查询不到-->就去DB查询;
return this.getUserInfo(sets);
}
功能3:我的粉丝
@ApiOperation(value="查看我的粉丝")
@GetMapping(value = "/myFollower")
public List<UserVO> myFollower(Integer userId){
return this.relationService.myFollower(userId);
}
/**
* 查看我的粉丝
*/
public List<UserVO> myFollower(Integer userId){
SetOperations<String, Integer> opsForSet = redisTemplate.opsForSet();
//粉丝集合
String followerkey=Constants.CACHE_KEY_FOLLOWER+userId;
Set<Integer> sets= opsForSet.members(followerkey);
//sets==null,查询不到就去DB查
return this.getUserInfo(sets);
}
功能4:获取用户信息
private Object getUserInfo(Collection<Integer> members) {
ArrayList<Object> list = new ArrayList<>();
for (Integer userId : members){
Map map = redisTemplate.opsForHash().entries(USER_KEY + userId);
User user = new User();
user.setUsername((String) map.get("username"));
user.setSex((Integer) map.get("sex"));
user.setId((Integer) map.get("id"));
list.add(user);
}
return list;
}
微关系计算
一、计算好友关系业务场景分析
微博微关系:
共同关注:是计算出我和雷军共同关注的人有哪些?
我关注的人也关注他:是计算出我关注的人群中,有哪些人同时和我一样关注了雷军
二、计算好友关系的redis技术方案
思考题:如果是采用数据库来实现用户的关系,一般SQL怎么写? 例如 阿甘关注10个人,雷军关注100个人,让你计算2人的共同关注那些人?
SQL的写法,一般是采用in 或 not in 来实现。但是对于互联网高并发的系统来说,in not in 明显不适合。
一般的做法是采用redis的set集合来实现。
Redis Set数据结构,非常适合存储好友、关注、粉丝、感兴趣的人的集合。然后采用set的命令就能得出我们想要的数据。
1. sinter命令:获得A和B两个用户的共同好友
2. sismember命令:判断C是否为B的好友
3. scard命令:获取好友数量
SpringBoot+Redis 计算微博好友关系
功能一:共同关注
@ApiOperation(value="求2个用户的关注交集")
@GetMapping(value = "/intersect")
public List<UserVO> intersect(Integer userId1, Integer userId2){
return this.relationService.intersect(userId1,userId2);
}
public List<UserVO> intersect(Integer userId1,Integer userId2){
SetOperations<String, Integer> opsForSet = redisTemplate.opsForSet();
String followeekey1 = Constants.CACHE_KEY_FOLLOWEE + userId1;
String followeekey2 = Constants.CACHE_KEY_FOLLOWEE + userId2;
//求2个集合的交集
Set<Integer> sets= opsForSet.intersect(followeekey1,followeekey2);
return this.getUserInfo(sets);
}
我关注的人也关注他
@ApiOperation("我关注的人也关注他")
@PostMapping("/getRelation2")
public Object getRelation2(Integer userId, Integer otherUserId){
return service.getRelation2(userId,otherUserId);
}
@Transactional
public Object getRelation2(Integer userId, Integer otherUserId) {
String concernKey=CONCERN_KEY+userId;
Set<Integer> concernList = redisTemplate.opsForSet().members(concernKey);
List<Integer> resultIds = new ArrayList<>();
for (Integer concernId : concernList){
Boolean flag = redisTemplate.opsForSet().isMember(CONCERN_KEY + concernId, otherUserId);
if (flag){
resultIds.add(concernId);
}
}
return this.getUserInfo(resultIds);
}
HyperLogLog
命令使用
HyperLogLog 命令详解
HyperLogLog 目前只支持3个命令,PFADD、PFCOUNT、PFMERGE
PFADD
将元素加入到HyperLogLog数据结构中,如果 HyperLogLog 的基数估算值在命令执行之后出现了变化,那么命令返回1,否则返回0。
PFCOUNT
返回给定 HyperLogLog 的基数估算值。
127.0.0.1:6379> pfadd uv 192.168.1.100 192.168.1.101 192.168.1.102 192.168.1.103
(integer) 1
127.0.0.1:6379> pfadd uv 192.168.1.100
(integer) 0
127.0.0.1:6379> pfcount uv
(integer) 4
PFMERGE
把多个HyperLogLog合并为一个HyperLogLog,合并后的HyperLogLog的基数是通过所有的HyperLogLog进行并集后,得出来的。
127.0.0.1:6379> pfadd uv1 192.168.1.100 192.168.1.101 192.168.1.102 192.168.1.103
(integer) 1
127.0.0.1:6379> pfcount uv1
(integer) 4
127.0.0.1:6379> pfadd uv2 192.168.1.100 192.168.2.101 192.168.2.102 192.168.2.103
(integer) 1
127.0.0.1:6379> pfadd uv3 192.168.1.100 192.168.3.101 192.168.3.102 192.168.3.103
(integer) 1
127.0.0.1:6379> pfmerge aganuv uv1 uv2 uv3
OK
127.0.0.1:6379> pfcount aganuv
(integer) 10
这个命令很重要,例如你可以统计一周 或 一个月的uv 就可以使用此命令来轻易实现。
基于Redis的UV计算
步骤1:模拟UV访问
@Service
@Slf4j
public class TaskService {
@Autowired
private RedisTemplate redisTemplate;
/**
*模拟UV访问
*/
@PostConstruct
public void init(){
log.info("模拟UV访问 ..........");
new Thread(()->this.refreshData()).start();
}
/**
* 刷新当天的统计数据
*/
public void refreshDay(){
Random rand = new Random();
String ip=rand.nextInt(999)+"."+
rand.nextInt(999)+"."+
rand.nextInt(999)+"."+
rand.nextInt(999);
//redis 命令 pfadd
long n=this.redisTemplate.opsForHyperLogLog().add(Constants.PV_KEY,ip);
log.debug("ip={} , returen={}",ip,n);
}
/**
* 按天模拟UV统计
*/
public void refreshData(){
while (true){
this.refreshDay();
//TODO 在分布式系统中,建议用xxljob来实现定时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
步骤2:实现UV统计功能
@RestController
@Slf4j
public class Controller {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping(value = "/uv")
public long uv() {
//redis命令 pfcount
long size=this.redisTemplate.opsForHyperLogLog().size(Constants.PV_KEY);
return size;
}
}