碰到这样一个需求,需求方需要根据一个人的联系方式得到一度联系人的一度联系人,即二度关系人脉的存储,就是我们朋友的朋友,并且要求一下子把一二度联系人全部返回。这种关系就像QQ、支付宝好友中可能认识的人,抖音中关注他的人也关注了他。
由于关系人非常多,至少也得四五个亿,自然想到Hbase作为存储。并且是一下把一二度关系人全部返回,即我们每一个朋友的朋友都要返回,这就决定了我们不能单一关系存储,因为单一关系查询次数过多,这是我们所不能忍受的,所以要把一二度联系人存储作为Value放在在一起,这样根据传入人就可以直接返回其一二度关系人,无需多次查询。
一、 存储结构分析
其存储结构如下:
rowkey:手机号+姓名+时间戳+5位随机数 value:一度联系人手机号_姓名,二度联系人手机号_姓名
存储如下示例:
rowkey:17515081745小明155576871200012345 value:17615081745_小强,13515081745_小李
rowkey:17515081745小明155576871200015456 value:17615081745_小强,15515081745_小王
rowkey:17515081745小明155576871200010256 value:17715081745_张三,18515081745_小亮
rowkey:17515081745小明155576871200015892 value:17715081745_张三,13515081745_李四
rowkey:17515081745小明155576871200055892 value:17715081745_张三,15615081745_王五
rowkey:18515081745张无忌155576871200013457 value:15515081745_张三丰,12515081745_大师伯
rowkey:18515081745张无忌155576871200002555 value:15515081745_张三丰,12515081745_二师伯
rowkey:18515081745张无忌155576871200012345 value:15515081745_张三丰,14515081745_三师伯
rowkey:16615081745普京155576871200012345 value:15615081745_特朗普,15715081745_奥巴马
二、存储关系分析
根据需求传入任何一个人的手机号码、姓名就可以返回其全部一度及对应二度联系人信息,如小明认识小强、小强认识小李
要查询-零度 | 一度联系人 |
小明 | 小强 |
小强 | 小李 |
存储时候得有以下排列组合:
要查询-零度 | 一度 | 二度 |
小明 | 小强 | 小李 |
小李 | 小强 | 小明 |
小强 | 小明 | 小明的一度关系人 |
小强 | 小李 | 小李的一度关系人 |
然而存储的时候需要每次上传的关系组与数据库中关系组进行对比,看存储的关系组中在数据库中是否已经存在,若存在还需要把待上传的关系组与数据库存在的关系组的一度联系人进行排列组合。如我们待上传的一个关系组是
小明 | 小强 |
小明 | 小李 |
此时,小明在已经存在数据库中存在其他关系组,如
小明 | 张三 | 李四 |
小明 | 王五 | 赵六 |
小强 | 张飞 | 赵七 |
小李 | 张飞 | 赵七 |
这时候就需要查询小明是否数据库中存在,若存在待存入的小明一度关系人小 、小李还要和数据库中小明的一度关系人张三、王五组合。同时还要查询一度关系人小强、小李是在数据库中否存在,若存在,待存入的小强、小李一度关系人小明,还要和其已存在的一度关系人张飞组合。
新存储的需要如下关系组合
小强 | 小明 | 小李 |
小李 | 小明 | 小强 |
小强 | 小明 | 张三 |
张三 | 小明 | 小强 |
小李 | 小明 | 王五 |
王五 | 小明 | 小李 |
小明 | 小强 | 张飞 |
张飞 | 小强 | 小明 |
小明 | 小李 | 张飞 |
张飞 | 小李 | 小明 |
三、响应结构示例
例如,我们根据17515081745小明155576871200012345查询小明的一二度关系人,我们要把查询出的二度关系人按手机号_姓名分组合并后返回给客户端。部分代码如下:
QueryOneLevelRespVo respVo = new QueryOneLevelRespVo();//响应实体
respVo.setPhoneNumber(phoneNumber);//传入人号码
respVo.setPhoneName(phoneName);//传入人姓名
List<String> contactLists = hbaseUtil.scan(table, startRow,stopRow);
List<KVContact> kvList = new ArrayList<>();
//将转化为K,V实体,利用Stream进行分组合并
for (String string : contactLists) {
String[] oneRelationArr = string.split(",");
KVContact kv = new KVContact();
kv.setKey(oneRelationArr[0]);//一度联系人
if (oneRelationArr.length==2) {
kv.setValue(oneRelationArr[1]);//二度联系人
}
kvList.add(kv);
}
//利用Java8的Stream进行分组合并
Map<String, List<String>> listMap = kvList.stream()
.collect(Collectors.groupingBy(KVContact::getKey,Collectors.mapping(KVContact::getValue, Collectors.toList())));
//组装联系人
List<RespConnections> respConnectionsList = new ArrayList<QueryOneLevelRespVo.RespConnections>();//一度联系人集合
List<Contacts> contactList = null;
RespConnections respConnection = null;
for (Map.Entry<String, List<String>> m : listMap.entrySet()) {
String[] oneRelationArr = m.getKey().split("_");//组装一度联系人
respConnection = new RespConnections();
respConnection.setPhone(oneRelationArr[0]);
respConnection.setName(oneRelationArr[1]);
//二度联系人集合
contactList = new ArrayList<QueryOneLevelRespVo.RespConnections.Contacts>();
Contacts contacts = null;
for (String str : m.getValue()) {
if (str!=null) {
String[] twoRelationArr = str.split("_");//组装二度联系人
contacts = new Contacts();
contacts.setPhone(twoRelationArr[0]);
contacts.setName(twoRelationArr[1]);
contactList.add(contacts);
}
}
respConnection.setContacts(contactList);
respConnectionsList.add(respConnection);
}
respVo.setConnections(respConnectionsLis;
响应实体类QueryOneLevelRespVo
import java.io.Serializable;
import java.util.List;
public class QueryOneLevelRespVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 手机号
*/
private String phoneNumber;
/**
* 手机号
*/
private String phoneName;
/**
* 一度联系人集合
*/
private List<RespConnections> connections;
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPhoneName() {
return phoneName;
}
public void setPhoneName(String phoneName) {
this.phoneName = phoneName;
}
public List<RespConnections> getConnections() {
return connections;
}
public void setConnections(List<RespConnections> connections) {
this.connections = connections;
}
public static class RespConnections{
/**
* 一度联系人号码
*/
private String phone;
/**
* 一度联系人姓名
*/
private String name;
/**
* 二度联系人集合
*/
private transient List<Contacts> contacts;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Contacts> getContacts() {
return contacts;
}
public void setContacts(List<Contacts> contacts) {
this.contacts = contacts;
}
public static class Contacts {
/**
* 二度联系人姓名
*/
private String name;
/**
* 二度联系人手机号
*/
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
}
}