redis除了咱们经常使用的5中基本数据类型,在此基础上,还提供了一些特殊的功能模块。这里介绍如下三种:bitmaps,hyperloglog,geo。java
概述git
redis中的bitmaps(位图)不是实际的数据类型,而是在String类型上定义的一组面向位的操做。因为字符串是二进制安全blob,而且它们的最大长度为512 MB,所以它们适合设置最多2^32个不一样的位。位图的最大优点之一是它们在存储信息时一般能够节省大量空间。例如,在经过增量用户ID表示不一样用户的系统中,可使用仅512MB的存储器记住40亿用户的单个位信息redis
使用场景算法
活跃用户数统计:假设一个系统天天有100万独立用户登陆。数组
需求1:统计每周的活跃用户数。安全
需求2:统计一周天天都登陆了用户数。bash
分析,须要存储每一个用户的每一天的登陆信息数据结构
经常使用命令dom
setbit key offset value
bitop 在不一样的字符串之间执行逐位操做。提供的操做是AND,OR,XOR和NOT。
bitcount 执行填充计数,报告设置为1的位数。
bitpos 查找具备指定值0或1的第一个位。
复制代码
代码学习
//TODO
public class BitMapTest {
public static void main(String[] args) {
//链接本地的 Redis 服务
Jedis jedis = new Jedis("localhost");
//初始化id为0-99的签到状况
IntStream.range(0, 100).forEach((id) -> {
jedis.setbit("星期" + id % 7, id, true);
}
);
//id的为10的用户,天天都登陆了的。
for (int i = 0; i < 7; i++) {
jedis.setbit("星期" + i , 10, true);
}
for (int i = 0; i < 7; i++) {
System.out.println(String.format("星期%s 的活跃用户数为%s",i, jedis.bitcount("星期" + i)));
}
jedis.bitop(BitOP.OR, "week","星期0", "星期1", "星期2", "星期3", "星期4", "星期5", "星期6");
System.out.println(String.format("本周的活跃用户数为%s",jedis.bitcount("week")));
jedis.bitop(BitOP.AND, "week1","星期0", "星期1", "星期2", "星期3", "星期4", "星期5", "星期6");
System.out.println(String.format("本周天天都登陆的用户数%s",jedis.bitcount("week1")));
}
}
复制代码
总结
学习bitmap,咱们知道bitmap经过一个bit数组来存储特定数据的一种数据结构,每个bit位都能独立包含信息,bit是数据的最小存储单位,所以能大量节省空间。bitmap有一个很明显的优点是能够轻松合并多个统计结果,只须要对多个结果求与,或,异或等操做就能够。也能够大大减小存储内存,能够作个简单的计算,若是要统计1亿个数据的基数值,大约须要内存。
数据类型 | 占用空间 | 储存的用户量 | 内存量 |
---|---|---|---|
set | 32位 | 100000000 | 32*100000000/8/1024/1024≈ 381M |
bitMap | 1位 | 100000000 | 100000000/8/1024/1024 ≈ 12M |
概述
什么是基数? 假设有个集合为(1,4,2,7,8,7)那么这个集合的基数为去重以后的元素个数,即为5。
bitmap对于内存的节约量是显而易见的,但仍是不够。统计一个对象的基数值须要12M,若是统计10000个对象,就须要将近120G了,一样不能普遍用于大数据场景。
redis中实现的HyperLogLog,只须要12K内存,在标准偏差0.81%的前提下,可以统计2^{64}个数据。HyperLogLog是一种几率算法,几率算法不直接存储数据集合自己,经过必定的几率统计方法预估基数值,这种方法能够大大节省内存,同时保证偏差控制在必定范围内。
经常使用命令
pfadd key val1 val2 ...
pfcount key 统计基数
复制代码
使用场景
统计页面独立的UV,UV即对于某个页面,天天独立访问的用户数量,若是同一个用户屡次访问,只能算一次。
代码
public class HyperLogLogTest {
public static void main(String[] args) {
//链接本地的 Redis 服务
Jedis jedis = new Jedis("localhost");
//初始化id为0-99的签到状况
IntStream.range(0, 10000).forEach((id) -> {
String userId = UUID.randomUUID().toString();
jedis.pfadd("datetime:page1",userId);
}
);
System.out.println("经过hyperloglog几率算法估算出的UV:"+jedis.pfcount("datetime:page1"));
}
}
复制代码
总结
hyperloglog是一种几率算法,是经过局部推算总体的一种算法,在不存储元素的状况下,用计算集合的基数,可是有必定的偏差。若是偏差在业务容忍的范围内。那么这一种很是节省内存的高效算法。
概述
在redis3.2版本,增长了Geo地理空间位置的计算功能。经过GEO咱们能够计算两个地理位置的距离,以及给定地理位置获取指定范围内的地理位置集合等
经常使用命令(使用redis3.2以后的版本才有geo功能)
一、GEOADD:增长某个地理位置的坐标 二、GEOPOS:获取某个地理位置的坐标 三、GEODIST:获取两个地理位置的距离 四、GEORADIUS:根据给定地理位置坐标获取指定范围内的地理位置集合 五、GEORADIUSBYMEMBER:根据给定地理位置获取指定范围内的地理位置集合 六、GEOHASH:获取某个地理位置的 geohash 值
使用场景
计算用户和商家的距离
代码
public class GeoTest {
public static void main(String[] args) {
//链接本地的 Redis 服务
Jedis jedis = new Jedis("localhost");
//初始化4个门店的位置
Map<String, GeoCoordinate> map=new HashMap<>();
map.put("成华新风路专营店",new GeoCoordinate(104.11117,30.6846));
map.put("青羊区东门街营业厅",new GeoCoordinate(104.05983,30.66685));
map.put("武侯区一环路南三段营业厅",new GeoCoordinate(104.0614,30.63354));
map.put("金牛区三洞桥专营店",new GeoCoordinate(104.04903,30.67408));
jedis.geoadd("shop",map);
GeoRadiusParam param=GeoRadiusParam.geoRadiusParam()
.withDist()//返回距离
.withCoord() //返回经纬度
.sortAscending();//根据距离升序排序;
double userLongitude=104.045181;//用户的经度
double userLatitude=30.688663;//用户的纬度
//查询距离用户10千米范围内的营业点
List<GeoRadiusResponse> shop = jedis.georadius("shop", userLongitude, userLatitude, 10, GeoUnit.KM,param);
for (GeoRadiusResponse geoRadiusResponse : shop) {
System.out.println(String.format("用户距离 %s (经度:%s,纬度:%s) 有%s 公里 ",
geoRadiusResponse.getMemberByString(),
geoRadiusResponse.getCoordinate().getLongitude(),
geoRadiusResponse.getCoordinate().getLatitude(),
geoRadiusResponse.getDistance()));
}
}
}
复制代码
总结
redis的geo是经过 它只是假设地球是一个球体,由于使用的距离公式是Haversine公式。这个公式只是在应用于地球时的近似值,而地球不是一个完美的球体。在做为不是特别高的精度状况下,使用geo是一个不错的选择。