在用关系型数据库(Mysql)设计粉丝关注表时,感觉要写很多判断,比如:
新增关注时,要判断之前的关联是否存在,如果不存在才插入。
又或者获取粉丝数量或关注的人数量,一般做法是在数据库里查出来再存到Redis,新增关注后再用redis的incr命令+1,在查询的过程中又要考虑索引问题。。。总之比较的麻烦
后来有考虑过用redis的的集合(Set)来存粉丝和关注的人,但问题是集合是无序的,想要获取关注时间就无能为力了。
最后用Redis的有序集合解决了上述的所有问题,有顺集合中有一个score分数,会根据这个分数有序排列,我们直接把关注时间当成这条数据的分数,这样就有搞定了,还能根据分数取范围、分页、排序等操作。于是将粉丝、关注的人全部存入redis中,但又担心redis挂了,数据还没来得及写入到磁盘导致数据丢失的问题,我又在写入redis的时候同步写了一份到mysql。
相关命令:
zRank:判断指定key的分数,我主要用来判断某个人是否在集合内
multi和exec命令,这两个是用来写事务操作的,比如我关注了张三,会创建两个有序集合,一个是我关注的人,第一个是张三的粉丝
zCount:根据分数统计集合内的数量,我用来统计新增的数丝数
zRem:删除成员,用于取关
zCard:获取集合内所有成员,用于统计粉丝数和关注人数
zRevRange:用于获取关注者列表、数丝列表,按分数(关注时间)倒序排列,还可以分页
以下是相关代码,仅做参考
<?php
namespace app\common\model;
class Follow extends \think\Model
{
protected $table = 'xxxxxxx';
/**
* 是否关注了某人
*
* @param int $userId
* @param int $followerId
*
* @return bool
*/
public function isFollow(int $userId, int $followerId): bool
{
$key = Cache::UserFollow . $userId;
$rank = Cluster::init()->zRank($key, $followerId);
return $rank !== false;
}
/**
* 添加关注
*
* @param int $userId
* @param int $followerId
*
* @return bool
*/
public function addFollow(int $userId, int $followerId): bool
{
$key = Cache::UserFollow . $userId;
$key2 = Cache::UserFans . $followerId;
$subscribeTime = time();
$redis = Cluster::init()->multi();
$result = $redis->zAdd($key, $subscribeTime, $followerId)
->zAdd($key2, $subscribeTime, $userId)
->exec();
// 写入DB作为备份
if ($result[0] === 1 && $result[1] === 1) {
$insertDb = [
'user_id' => $userId,
'follower_id' => $followerId,
'status' => 1,
'create_time' => $subscribeTime
];
$this->table($this->table)->insert($insertDb);
}
// 设置第一个用户关注时间,用于统计新增粉丝数
Cluster::init()->setnx($this->getFansFirstTimeKey($followerId), time());
return true;
}
/**
* 重置未读新增粉丝的起始时间
* 如果粉丝,才新增该key(避免每个人都生成一个无意义的key)
*
* @param int $userId
*/
public function resetFansFirstTime(int $userId)
{
Cluster::init()->set($this->getFansFirstTimeKey($userId), time(), ['xx']);
}
/**
* 获取未读新增粉丝的起始时间
*
* @param int $userId
*
* @return int
*/
public function getFansFirstTime(int $userId): int
{
return intval(Cluster::Get($this->getFansFirstTimeKey($userId)));
}
/**
* 获取新增粉丝数量
*
* @param int $userId
*
* @return int
*/
public function getNewFansNum(int $userId): int
{
$firstTime = $this->getFansFirstTime($userId);
$key = Cache::UserFans . $userId;
return Cluster::init()->zCount($key, $firstTime, time());
}
/**
* 取消关注
*
* @param int $userId
* @param int $followerId
*
* @return bool
*/
public function unFollow(int $userId, int $followerId): bool
{
$key = Cache::UserFollow . $userId;
$key2 = Cache::UserFans . $followerId;
$redis = Cluster::init()->multi();
$result = $redis->zRem($key, $followerId)
->zRem($key2, $userId)
->exec();
// 写入DB作为备份
if ($result[0] === 1 && $result[1] === 1) {
$this->table($this->table)
->where("user_id", $userId)
->where("follower_id", $followerId)
->setField("status", 2);
}
return true;
}
/**
* 获取我的粉丝列表
*
* @param int $userId
* @param int $offset
* @param int $limit
*
* @return array
*/
public function FansList(int $userId, int $page, int $pageSize)
{
$key = Cache::UserFans . $userId;
return $this->getList($key, $page, $pageSize);
}
/**
* 获取我的关注列表
*
* @param int $userId
* @param int $offset
* @param int $limit
*
* @return array
*/
public function FollowList(int $userId, int $page, int $pageSize): array
{
$key = Cache::UserFollow . $userId;
return $this->getList($key, $page, $pageSize);
}
/**
* 获取粉丝数量
*
* @param int $userId
*
* @return int
*/
public function getFansNum(int $userId): int
{
$key = Cache::UserFans . $userId;
return Cluster::init()->zCard($key);
}
/**
* 获取我关注的人数量
*
* @param int $userId
*
* @return int
*/
public function getFollowNum(int $userId): int
{
$key = Cache::UserFollow . $userId;
return Cluster::init()->zCard($key);
}
private function getList(string $key, int $page, int $pageSize)
{
$offset = ($page - 1) * $pageSize;
return Cluster::init()->zRevRange($key, $offset, $offset + $pageSize - 1, true);
}
private function getFansFirstTimeKey(int $userId)
{
return Cache::UserFansFirstTime . $userId;
}
}