目录
文章的推送
由于智能排序需要算法等 ,该项目只能适用于最简单的Timeline模式
Timeline模式
拉模式
实现思路:
就是关注的对象发布了动态,粉丝在去他们的发件箱拉取出来,按时间进行排序,显示到自己的收件箱里面
推模式
实现思路:
博主发布动态直接推给关注了自己的粉丝
推拉结合模式
实现思路:
主要是区分普通粉丝和活跃粉丝,活跃的粉丝采取推的模式(因为拉的模式有延迟,而活跃粉丝喜欢经常刷新),普通的粉丝采取拉的模式,比如一些僵尸粉丝就完全可以不消耗任何内存来进行存放。
三种模式的对比
由于该项目,用户量达不到千万级别,所以采用推的模式相对更好。
推模式的代码实现:
看需求:
代码就不写了 逻辑还是那样
根据需求选择合适的数据类型,涉及到排序我们可以选择List和sortedlist.
选取的依据:
我们后续涉及到分页的实现:我们在进行分页查询的时候,会有up主进行发布消息,如果你关注的很多,那这个频率是很快的,所以如果采用传统的分页模式会有很大的影响。如下图所示:
应该采用滚动的方式,进行数据的读取
原因
如果采用list 只能左右出去,然后你取的时候是给下标的,此时如果有新的进来,必然会扰乱你的下标。而List又没有其他的方式进行读取。所以不合适。
相对的采用soterlist,他可以根据你传的分值进行排序,并且取得时候,可以根据分值得范围进行选取,只要记录你上次取得分值(时间戳)从他下面取对应得size即可。
解决:
这是采用角标进行查询:如果遇到更新则会发生这种情况 不可取
这是采用分数进行查询得:避免了重复刷新得问题。
语法
1000到时候 换成当时得时间戳即可
limit 最后那个是 数量嘛 前面得那个是偏移量
比如 0表示 1000-0 左闭右闭
1得话 就是偏移一个 左开右闭。
还是有可能出现错误
如果出现了相同值 ,比如下面这个两个6 偏移量1 他还会出现6
因为他检索到第一个6就停止了 +偏移量输出后面得
参数得规律:
代码
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// 1.获取当前用户
Long userId = UserHolder.getUser().getId();
// 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
String key = FEED_KEY + userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 2);
// 3.非空判断
if (typedTuples == null || typedTuples.isEmpty()) {
return Result.ok();
}
// 4.解析数据:blogId、minTime(时间戳)、offset
List<Long> ids = new ArrayList<>(typedTuples.size());
long minTime = 0; // 2
int os = 1; // 2
for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
// 4.1.获取id
ids.add(Long.valueOf(tuple.getValue()));
// 4.2.获取分数(时间戳)
long time = tuple.getScore().longValue();
if(time == minTime){
os++;
}else{
minTime = time;
os = 1;
}
}
// 5.根据id查询blog
String idStr = StrUtil.join(",", ids);
List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
for (Blog blog : blogs) {
// 5.1.查询blog有关的用户
queryBlogUser(blog);
// 5.2.查询blog是否被点赞
isBlogLiked(blog);
}
// 6.封装并返回
ScrollResult r = new ScrollResult();
r.setList(blogs);
r.setOffset(os);
r.setMinTime(minTime);
return Result.ok(r);
}
附近商店
采用得数据结构
GEO底层就是一个sortedSet,输进去得经纬度它通过自己得算法,转换成分值,需要计算得时候在转换成经纬度。
代码:
@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
// 1.判断是否需要根据坐标查询
if (x == null || y == null) {
// 不需要坐标查询,按数据库查询
Page<Shop> page = query()
.eq("type_id", typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
// 返回数据
return Result.ok(page.getRecords());
}
// 2.计算分页参数
int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
// 3.查询redis、按照距离排序、分页。结果:shopId、distance
String key = SHOP_GEO_KEY + typeId;
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() // GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE
.search(
key,
GeoReference.fromCoordinate(x, y),
new Distance(5000),
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
);
// 4.解析出id
if (results == null) {
return Result.ok(Collections.emptyList());
}
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
if (list.size() <= from) {
// 没有下一页了,结束
return Result.ok(Collections.emptyList());
}
// 4.1.截取 from ~ end的部分
List<Long> ids = new ArrayList<>(list.size());
Map<String, Distance> distanceMap = new HashMap<>(list.size());
list.stream().skip(from).forEach(result -> {
// 4.2.获取店铺id
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
// 4.3.获取距离
Distance distance = result.getDistance();
distanceMap.put(shopIdStr, distance);
});
// 5.根据id查询Shop
String idStr = StrUtil.join(",", ids);
List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
for (Shop shop : shops) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
// 6.返回
return Result.ok(shops);
}
签到
采用得数据结构:
由于使用数据库进行存放得话,会有过多的内存消耗,所以采用redis缓存进行存放:
BitMap:
采用年月一起作为key进行存放,记录31位1,0即可。
基本用法:
需求:
签到得代码:
@Override
public Result sign() {
// 1.获取当前登录用户
Long userId = UserHolder.getUser().getId();
// 2.获取日期
LocalDateTime now = LocalDateTime.now();
// 3.拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + keySuffix;
// 4.获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
// 5.写入Redis SETBIT key offset 1
stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.ok();
}
签到统计
就是统计从指定得天数开始,连续签了几天 qq达人那种断了就没了
代码:
@Override
public Result signCount() {
// 1.获取当前登录用户
Long userId = UserHolder.getUser().getId();
// 2.获取日期
LocalDateTime now = LocalDateTime.now();
// 3.拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + keySuffix;
// 4.获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
// 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
List<Long> result = stringRedisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
);
if (result == null || result.isEmpty()) {
// 没有任何签到结果
return Result.ok(0);
}
Long num = result.get(0);
if (num == null || num == 0) {
return Result.ok(0);
}
// 6.循环遍历
int count = 0;
while (true) {
// 6.1.让这个数字与1做与运算,得到数字的最后一个bit位 // 判断这个bit位是否为0
if ((num & 1) == 0) {
// 如果为0,说明未签到,结束
break;
}else {
// 如果不为0,说明已签到,计数器+1
count++;
}
// 把数字右移一位,抛弃最后一个bit位,继续下一个bit位
num >>>= 1;
}
return Result.ok(count);
}
UV统计(用户访问量,一个人记一次)
就像那QQ空间得那个访问量一样
与之相对得是PV统计:
采用得数据结构
也可以用set进行存放,但是由于访问量这个基数会很大,所以采用HyperLogLog进行存放,但是这个方法虽然内存占用小但是存在0.81%得误差,如果需求是要求不允许出现这么大误差得话,建议使用set。
基本得用法实列,和set差不多也是唯一性。
代码实现:
模拟插入
@Test
void testHyperLogLog() {
String[] values = new String[1000];
int j = 0;
for (int i = 0; i < 1000000; i++) {
j = i % 1000;
values[j] = "user_" + i;
if(j == 999){
// 发送到Redis
stringRedisTemplate.opsForHyperLogLog().add("Code-zyc", values);
}
}
// 统计数量
Long count = stringRedisTemplate.opsForHyperLogLog().size("Code-zyc");
System.out.println("count = " + count);
}
HyperLogLog插入之前的内存:
HyperLogLog插入之后的内存以及结果:
运行结果:
运行后内存:
对于一个key而言 确实只占用了14kb的内存
Set插入之前的内存:
代码看一下 证明真测试了
内存情况:
Set插入之后的内存以及结果:
结果:完全正确 没有统计错误
内存情况:
这个就不用计算机算了吧,这完全以及很大了。