1、引言
基本上以陌生人社交为主的IM产品里,都会增加“附近的人”、“附近的xxx”这种以LBS(地理位置)为导向的产品特色(微信这个熟人社交产品里为啥也有“附近的人”?这当然是历史原因了,微信当初还不是想借此引流嘛。。。),因为“附近的xxx”这种类似功能在产品运营早期,对于种子用户的积累有很大帮助(必竟某种需求,对于人类来说,是上帝赋予的最原始冲动,你懂的...)。
比如下图中的几款主流移动端IM中的“附近的xxx”功能:
那么,对于很多即时通讯(IM)的开发者初学者来说,“附近的人”或者类似功能,在技术实现上还有点摸不着头脑。本文将简要的为你讲解“附近的人”的基本理论原理,并以Redis的GEO系列地理位置操作指令为例,理论联系实际地为你讲解它们是如何被高效实现的。
阅读提示:本文适合有一定Redis使用经验的服务器后端开发人员阅读,IM移动客户端开发人员没有太多阅读的必要(理论原理倒是可以知道一下),必竟“附近的xxx”功能主要工作在服务端,而不在客户端。
2、IM开发干货系列文章
本文是系列文章中的第19篇,总目录如下:
《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》
《一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)》
《IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理》
《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》
《IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
3、“附近的人”功能原理
其实,“附近的人”功能原理并不复杂。
它需要做以下两件事情:
1)所有使用该IM产品的人,在使用“附近的人”功能前提交自已的地理位置;
2)根据“我”的地理位置,计算出别人跟我的距离;
3)将第2步中计算出的距离由近及远,进行排序。
具体在产品技术上的实现原理,也很容易理解:
1)现在移动端(ios、android等),通过系统的API很容易抓到用户当前的位置(即经纬度数据);
2)根据第1步中的经纬度数据,很容易计算出两个点之间的距离(计算公式原理,可以百度一下,我的几何和数学知识都还给老师了,给你讲不了);
3)对第2步中的计算结果排序就更简单了,没什么好提的。
对于IM新手来说,可能对于第2步中的根据经纬度数据计算出两点距离,觉得有点难度,实际上根据数据公式(自已百度一下吧,有点复杂,哥不贴了),用代码来实现,只有短短的十来行代码。
下面是一个简单的Java版实现:
/**
* 计算地球上任意两点(经纬度)距离
*
* @param long1 第一点经度
* @param lat1 第一点纬度
* @param long2 第二点经度
* @param lat2 第二点纬度
* @return 返回距离 单位:米
*/
public static double Distance(double long1, double lat1, double long2, double lat2)
{
double a, b, R;
R = 6378137; // 地球半径
lat1 = lat1 * Math.PI / 180.0;
lat2 = lat2 * Math.PI / 180.0;
a = lat1 - lat2;
b = (long1 - long2) * Math.PI / 180.0;
double d;
double sa2, sb2;
sa2 = Math.sin(a / 2.0);
sb2 = Math.sin(b / 2.0);
d = 2* R * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1) * Math.cos(lat2) * sb2 * sb2));
return d;
}
在进行代码测试的时候,可以结合这个在线工具网页进行结果检验:http://www.hhlink.com/%E7%BB%8F%E7%BA%AC%E5%BA%A6/
嗯,看起来好简单!
4、自已从零实现的话,没有难度吗?
嗯,通过上一节的原理讲解,目前为止,看起来确实很简单。
但,如果自已从零实现的话,对于IM这种高性能、高并发场景来说,确实有一点难度,难不在移动客户端,而是在服务端。
技术难点主要包括:
1)如何高效地进行两点距离的计算,对于高并发服务端来说,像上一节中的代码那样,一个一个计算,还是有点不高效;
2)如何高效地进行地理围栏的圈定(难道是把所有当前在线的用户,离我的距离都一一算一遍,然后按距离进行筛选?那性能岂不是噩梦?)。
那,有救吗?答案是有!继续看下一节。
5、Redis里的GEO地理位置相关指令,就能很好的上述问题
针对“附近的人”这一位置服务领域的应用场景,服务端高性能场景下,常见的可使用PG、MySQL和MongoDB等多种DB的空间索引进行实现。
而Redis另辟蹊径,结合其有序队列zset以及geohash编码,实现了空间搜索功能,且拥有极高的运行效率。
要提供完整的“附近的人”这样的功能或服务,最基本的是要实现“增”、“删”、“查”的功能。本文余下的文字,以下将分别进行介绍,其中会重点对查询功能进行解析。并将从Redis源码角度对其算法原理进行解析,并推算查询时间复杂度。
Redis相关资源:
1)Redis官网:https://redis.io
2)Redis的GEO指令说明(英文):https://redis.io/commands
3)Redis的GEO指令说明(中文):http://redisdoc.com/geo/geoadd.html
6、Redis的GEO地理位置操作指令
自 Redis 3.2版 开始,Redis基于geohash和有序集合提供了地理位置相关功能。
Re