这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
一、前言
使用 jedis,测试环境:
pom.xml配置- 测试代码
pom.xml
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.2</version>
</dependency>
- 测试代码
public class JedisTest {
private Jedis jedis;
@Before
public void setUp() {
jedis = new Jedis("127.0.0.1", 6379);
}
@After
public void after() {
jedis.close();
}
@Test
public void testCache() {
// 最简单的设置缓存
jedis.set("key1", "value1");
System.out.println(jedis.get("key1"));
}
}
二、实战
(1)HyperLogLog
hyperloglog:是由数据结构 + 概率算法组合而成的,用于去重统计,近似数。
可以用于网站数据分析, PV、UV等,没必要过于精确。
举个栗子:统计 UV
如果基于 set 来计数,太耗费内存了,基于 HyperLogLog 算法来计数,是近似计数,有0.8%的误差,但是误差不会太大,可以给出一个相对准确的近似计数,而且就占用 12kb 的内存。
实战命令如下:
pfadd key:可以对数据进行计数,如果计算过这个元素,就返回0,没计算过就返回1,他也是可以去重的
pfcount key:可以获取计数结果
1)基于 HyperLogLog 的网站 UV 统计程序
实现如下:
public class JedisTest {
/**
* 初始化 UV 数据
*/
private void initUVData() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(new Date());
for (int i = 0; i < 1358; ++i) {
jedis.pfadd("hyperloglog_uv_" + today, String.valueOf(i + 1));
}
}
private long getUV2() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(new Date());
return jedis.pfcount("hyperloglog_uv_" + today);
}
@Test
public void testUV2() {
initUVData();
long uv = getUV2();
System.out.println("今天 UV 的值是:" + uv);
}
}
输出结果:
今天 UV 的值是:1366
2)网站重复垃圾数据的快速去重和过滤
对于你的一些网站,垃圾数据,多的是一些不良广告,在一些论坛或者别的,有的时候你会看到一些在帖子下面的评论里,是一些广告,都有,可能会遇到有人频繁的提交相同的垃圾信息到你的站点里。
实战命令如下:
pfadd key content:如果返回的是1,那么说明之前没见过这条数据,如果返回的是0,说明之前见过这条数据了
实现如下:
public class JedisTest {
/**
* 判断当强内容是否是垃圾内容
*
* @param content 内容
* @return 是否
*/
private Boolean isGarbageContent(String content) {
return jedis.pfadd("hyperloglog_content", content) == 0;
}
@Test
public void testGarbageContent() {
String content = "正常的内容";
System.out.println("是否为垃圾内容: " + (isGarbageContent(content) ? "是" : "否"));
content = "垃圾内容";
System.out.println("是否为垃圾内容: " + (isGarbageContent(content) ? "是" : "否"));
content = "垃圾内容";
System.out.println("是否为垃圾内容: " + (isGarbageContent(content) ? "是" : "否"));
}
}
输出结果:
是否为垃圾内容: 否
是否为垃圾内容: 否
是否为垃圾内容: 是
3)周活跃用户数、月活跃用户数、年活跃用户数的统计
实战命令如下:
pfmerge:统计合并多个hyperloglog,进行统一的去重,可以算出来周、月和年的活跃用户数。
实现如下:
public class JedisTest {
/**
* 初始化 UV 数据
* @param date 日期
*/
private void initUVData3(String date) {
Random random = new Random();
int startIndex = random.nextInt(1000);
System.out.println("今日访问uv起始id为:" + startIndex);
for (int i = startIndex; i < startIndex + 1358; ++i) {
for (int j = 0; j < 10; ++j) {
jedis.pfadd("hyperloglog_uv_" + date, String.valueOf((i + 1)));
}
}
}
private long getUV3(String date) {
return jedis.pfcount("hyperloglog_uv_" + date);
}
private long getWeeklyUV() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
List<String> keys = new ArrayList<>();
for (int i = 0; i < 7; ++i) {
calendar.add(Calendar.DAY_OF_YEAR, 1);
String date = dateFormat.format(calendar.getTime());
keys.add("hyperloglog_uv_" + date);
}
String [] keyArray = keys.toArray(new String[keys.size()]);
jedis.pfmerge("weekly_uv", keyArray);
return jedis.pfcount("weekly_uv");
}
@Test
public void testUV3() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
for (int i = 0; i < 7; ++i) {
calendar.add(Calendar.DAY_OF_YEAR, 1);
String date = dateFormat.format(calendar.getTime());
initUVData3(date);
System.out.println("日期为:" + date + "的uv值为:" + getUV3(date));
}
long weeklyUV = getWeeklyUV();
System.out.println("实际的周活跃用户数为:" + weeklyUV);
}
}
输出结果:
今日访问uv起始id为:133
日期为:2021-08-11的uv值为:1365
今日访问uv起始id为:47
日期为:2021-08-12的uv值为:1364
今日访问uv起始id为:362
日期为:2021-08-13的uv值为:1360
今日访问uv起始id为:906
日期为:2021-08-14的uv值为:1360
今日访问uv起始id为:893
日期为:2021-08-15的uv值为:1361
今日访问uv起始id为:196
日期为:2021-08-16的uv值为:1364
今日访问uv起始id为:714
日期为:2021-08-17的uv值为:1359
实际的周活跃用户数为:2217
(2)bitmap 位图
如果说你要记录一下,在系统 里执行一些特殊的操作,每天执行过某个操作的用户有多少个人,操作日志,审计日志,记录下来每个用户每天做了哪些操作,每个用户每天搞一个
set,里面放他做的一些操作日志,也可以对每种操作都搞一个set,里面放执行过这些操作的用户。
bitmap位图:二进制里的一位一位的。
可以把网站里每一种操作每天执行过的用户都放在一个位图里,一个用户仅仅对应了一位而已。
主要命令:
setbit key user_id 1:设置值getbit key user_id:获取值bitcount key:统计
一个位图统计100万用户的行为,也不过一百多kb的内存
实现如下:
public class JedisTest {
/**
* 记录用户的操作日志
* @param operation 操作
* @param userId 用户Id
*/
private void recordUserOperationLog(String operation, long userId) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(new Date());
jedis.setbit("operation::" + operation + "::" + today + "::log", userId, String.valueOf(1));
}
private Boolean hasOperated(String operation, long userId) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(new Date());
return jedis.getbit("operation::" + operation + "::" + today + "::log", userId);
}
@Test
public void testBit() {
recordUserOperationLog("操作1", 110);
System.out.println("用户110是否执行过操作:" + (hasOperated("操作1", 110) ? "是" : "否"));
System.out.println("用户111是否执行过操作:" + (hasOperated("操作1", 111) ? "是" : "否"));
}
}
输出结果:
用户110是否执行过操作:是
用户111是否执行过操作:否
(3)GeoHash
1)基于 GeoHash 计算你与商铺距离
redis 里还有一种特殊的数据结构,叫做 Geo,可以让你在里面添加很多的地理位置,每个位置对应一个名称和一个经纬度,然后可以利用这个数据结构计算各种距离的位置,获取方圆半径多少1公里内的店铺。
主要命令如下:
geoadd key longitude latitude:添加地理位置geodist key user shop unit='km':获取user和shop距离
实现如下:
public class JedisTest {
/**
* 添加地理位置
*
* @param name 名称
* @param longitude 经度
* @param latitude 纬度
*/
private void addLocation(String name, double longitude, double latitude) {
jedis.geoadd("location_data", longitude, latitude, name);
}
/**
* 获得用户到商家距离
* @param user 用户
* @param shop 商家
* @return 距离
*/
private double getDistance(String user, String shop) {
return jedis.geodist("location_data", user, shop, GeoUnit.KM);
}
@Test
public void testGeo() {
addLocation("张三", 116.49428833935545, 39.86700462665782);
addLocation("小吃店", 116.45961274121092, 39.87517301328063);
System.out.println("用户到商家的距离为:" + getDistance("张三", "小吃店"));
}
}
输出结果:
用户到商家的距离为:3.0963
2)陌生人社交APP里的查找附近的人功能
主要命令:
geoadd key longitude latitude:记录每个用户的地理位置georadiusbymember key user radius unit='km':radius设置为1,则附近1公里的用户都找出来
实现如下:
public class JedisTest {
/**
* 查找附近5公里内的店铺
* @return 店铺
*/
private List<GeoRadiusResponse> getNearbyShops() {
return jedis.georadiusByMember("location_data", "张三", 5.0, GeoUnit.KM);
}
@Test
public void testGeoShops() {
List<String> nearbyShops = new ArrayList<>();
List<GeoRadiusResponse> results = getNearbyShops();
for (GeoRadiusResponse result : results) {
String name = result.getMemberByString();
if (!"张三".equals(name)) {
nearbyShops.add(name);
}
}
System.out.println("附近5公里内的商家: " + nearbyShops);
}
}
输出结果:
附近5公里内的商家: [小吃店]