一、HyperLogLog
统计 PV
:每个网页一个独立的 Redis 计数器就可以了,这个计数器的 key 后缀加上当天的日期。这样来一个请求,incrby 一次,最终就可以统计出所有的 PV 数据。
统计UV
: UV 不一样,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求每一个网页请求都需要带上用户的 ID,无论是登陆用户还是未登陆用户都需要一个唯一 ID 来标识。
import org.springframework.data.redis.core.HyperLogLogOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class HyperLogService {
@Resource
private RedisTemplate<String, String> redisTemplate;
/**
* 记录用户访问
*
* @param user
*/
public long add(String Key, String user) {
HyperLogLogOperations<String, String> hyperLogLog = redisTemplate.opsForHyperLogLog();
return hyperLogLog.add(Key, user);
}
/**
* 统计当前 UV
*
* @return
*/
public long sum(String Key) {
HyperLogLogOperations<String, String> hyperLogLog = redisTemplate.opsForHyperLogLog();
return hyperLogLog.size(Key);
}
/**
* 删除当前 key
*/
public void clear(String Key) {
HyperLogLogOperations<String, String> hyperLogLog = redisTemplate.opsForHyperLogLog();
hyperLogLog.delete(Key);
}
}
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ThreadLocalRandom;
@Slf4j
public class HyperLogTools {
static class BitKeeper {
private int maxBits;
public void random() {
// 这里的随机数可以当成一个对象的hashCode。long value = new Object().hashCode() ^ (2 << 32);
long value = ThreadLocalRandom.current().nextLong(2L << 32);
int bits = lowZeros(value);
if (bits > this.maxBits) {
this.maxBits = bits;
}
}
/**
* 低位有多少个连续0
* 思路上 ≈ 倒数第一个1的位置
*
* @param value
* @return
*/
private int lowZeros(long value) {
int i = 1;
for (; i < 32; i++) {
if (value >> i << i != value) {
break;
}
}
return i - 1;
}
}
static class Experiment {
private int n;
private BitKeeper keeper;
public Experiment(int n) {
this.n = n;
this.keeper = new BitKeeper();
}
public void work() {
for (int i = 0; i < n; i++) {
this.keeper.random();
}
}
public void debug() {
log.info("%d %.2f %d\n", this.n, Math.log(this.n) / Math.log(2), this.keeper.maxBits);
}
}
public static void main(String[] args) {
for (int i = 1000; i < 100000; i += 100) {
Experiment exp = new Experiment(i);
exp.work();
exp.debug();
}
}
}
二、运行lua脚本工具
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Objects;
@Slf4j
public class ScriptUtil {
/**
* return lua script String
*
* @param path
* @return
*/
public static String getScript(String path) {
StringBuilder sb = new StringBuilder();
InputStream stream = ScriptUtil.class.getClassLoader().getResourceAsStream(path);
try (BufferedReader br = new BufferedReader(new InputStreamReader(Objects.requireNonNull(stream)))){
String str;
while ((str = br.readLine()) != null) {
sb.append(str).append(System.lineSeparator());
}
} catch (IOException e) {
log.error("执行异常:{}",e);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(ScriptUtil.getScript("limit.lua"));
}
}
三、获取一个类的注解,如果未获取到则获取父类
import org.joda.time.DateTime;
import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
public class ClassUtils {
/**
* 获取一个类的注解,如果未获取到则获取父类
*
* @param clazz 要获取的类
* @param annotation 注解类型
* @param <T> 注解类型泛型
* @return 注解
*/
public static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotation) {
T ann = clazz.getAnnotation(annotation);
if (ann != null) {
return ann;
} else {
if (clazz.getSuperclass() != Object.class) {
//尝试获取父类
return getAnnotation(clazz.getSuperclass(), annotation);
}
}
return ann;
}
/**
* 获取一个方法的注解,如果未获取则获取父类方法
*
* @param method 要获取的方法
* @param annotation 注解类型
* @param <T> 注解类型泛型
* @return
*/
public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotation) {
T ann = method.getAnnotation(annotation);
if (ann != null) {
return ann;
} else {
Class clazz = method.getDeclaringClass();
Class superClass = clazz.getSuperclass();
if (superClass != Object.class) {
try {
//父类方法
Method suMethod = superClass.getMethod(method.getName(), method.getParameterTypes());
return getAnnotation(suMethod, annotation);
} catch (NoSuchMethodException e) {
return null;
}
}
}
return ann;
}
public static Class<?> getGenericTypeByType(ParameterizedType genType, int index) {
Type[] params = genType.getActualTypeArguments();
if (index >= params.length || index < 0) {
return null;
}
Object res = params[index];
if (res instanceof Class) {
return ((Class) res);
}
if (res instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) res).getRawType();
}
return null;
}
/**
* 获取一个类的泛型类型,如果未获取到返回Object.class
*
* @param clazz 要获取的类
* @param index 泛型索引
* @return 泛型
*/
public static Class<?> getGenericType(Class clazz, int index) {
List<Type> arrys = new ArrayList<>();
arrys.add(clazz.getGenericSuperclass());
arrys.addAll(Arrays.asList(clazz.getGenericInterfaces()));
return arrys.stream()
.filter(Objects::nonNull)
.map(type -> {
if (clazz != Object.class && !(type instanceof ParameterizedType)) {
return getGenericType(clazz.getSuperclass(), index);
}
return getGenericTypeByType(((ParameterizedType) type), index);
})
.filter(Objects::nonNull)
.filter(res -> res != Object.class)
.findFirst()
.orElse((Class) Object.class);
}
/**
* 获取一个类的第一个泛型的类型
*
* @param clazz 要获取的类
* @return 泛型
*/
public static Class<?> getGenericType(Class clazz) {
return getGenericType(clazz, 0);
}
public static boolean instanceOf(Class clazz, Class target) {
if (clazz == null) {
return false;
}
if (clazz == target) {
return true;
}
if (target.isInterface()) {
for (Class aClass : clazz.getInterfaces()) {
if (aClass == target) {
return true;
}
}
}
if (clazz.getSuperclass() == target) {
return true;
} else {
if (clazz.isInterface()) {
for (Class aClass : clazz.getInterfaces()) {
if (instanceOf(aClass, target)) {
return true;
}
}
}
return instanceOf(clazz.getSuperclass(), target);
}
}
public static final String YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
/**
* 将对象转为指定的类型
* <br/>
* 支持日期,数字,boolean类型转换
*
* @param value 需要转换的值
* @param type 目标类型
* @return 转换后的值
*/
public static final <T> T cast(Object value, Class<T> type) {
if (value == null) {
return null;
}
Object newVal = null;
if (ClassUtils.instanceOf(value.getClass(), type)) {
newVal = value;
} else if (type == Integer.class || type == int.class) {
newVal = toInt(value);
} else if (type == Double.class || type == double.class || type == Float.class || type == float.class) {
newVal = toDouble(value);
} else if (type == Long.class || type == long.class) {
newVal = toLong(value);
} else if (type == Boolean.class || type == boolean.class) {
newVal = isTrue(value);
} else if (type == Date.class) {
newVal = formatUnknownString2Date(value.toString());
} else if (type == String.class) {
if (value instanceof Date) {
newVal = format(((Date) value), YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
} else {
newVal = String.valueOf(value);
}
}
return (T) newVal;
}
public static final Set<Class> basicClass = new HashSet<>();
static {
basicClass.add(int.class);
basicClass.add(double.class);
basicClass.add(float.class);
basicClass.add(byte.class);
basicClass.add(short.class);
basicClass.add(char.class);
basicClass.add(String.class);
}
public static boolean isBasicClass(Class clazz) {
return basicClass.contains(clazz);
}
public static int toInt(Object object) {
return toInt(object, 0);
}
/**
* 将对象转为int值,如果对象无法进行转换,则使用默认值
*
* @param object 要转换的对象
* @param defaultValue 默认值
* @return 转换后的值
*/
public static int toInt(Object object, int defaultValue) {
if (object instanceof Number) {
return ((Number) object).intValue();
}
if (isInt(object)) {
return Integer.parseInt(object.toString());
}
if (isDouble(object)) {
return (int) Double.parseDouble(object.toString());
}
return defaultValue;
}
/**
* 参数是否是有效整数
*
* @param obj 参数(对象将被调用string()转为字符串类型)
* @return 是否是整数
*/
public static boolean isInt(Object obj) {
if (StringUtils.isEmpty(obj)) {
return false;
}
if (obj instanceof Integer) {
return true;
}
return obj.toString().matches("[-+]?\\d+");
}
/**
* 字符串参数是否是double
*
* @param obj 参数(对象将被调用string()转为字符串类型)
* @return 是否是double
*/
public static boolean isDouble(Object obj) {
if (StringUtils.isEmpty(obj)) {
return false;
}
if (obj instanceof Double || obj instanceof Float) {
return true;
}
return compileRegex("[-+]?\\d+\\.\\d+").matcher(obj.toString()).matches();
}
/**
* 编译后的正则表达式缓存
*/
private static final Map<String, Pattern> PATTERN_CACHE = new ConcurrentHashMap<>();
/**
* 编译一个正则表达式,并且进行缓存,如果换成已存在则使用缓存
*
* @param regex 表达式
* @return 编译后的Pattern
*/
public static final Pattern compileRegex(String regex) {
Pattern pattern = PATTERN_CACHE.get(regex);
if (pattern == null) {
pattern = Pattern.compile(regex);
PATTERN_CACHE.put(regex, pattern);
}
return pattern;
}
/**
* 将对象转为Double,如果对象无法转换,将使用默认值0
*
* @param object 要转换的对象
* @return 转换后的值
*/
public static double toDouble(Object object) {
return toDouble(object, 0);
}
/**
* 将对象转为Double,如果对象无法转换,将使用默认值
*
* @param object 要转换的对象
* @param defaultValue 默认值
* @return 转换后的值
*/
public static double toDouble(Object object, double defaultValue) {
if (object instanceof Number) {
return ((Number) object).doubleValue();
}
if (isNumber(object)) {
return Double.parseDouble(object.toString());
}
if (null == object) {
return defaultValue;
}
return 0;
}
/**
* 参数是否是有效数字 (整数或者小数)
*
* @param obj 参数(对象将被调用string()转为字符串类型)
* @return 是否是数字
*/
public static boolean isNumber(Object obj) {
if (obj instanceof Number) {
return true;
}
return isInt(obj) || isDouble(obj);
}
/**
* 将对象转为 long值,如果无法转换,则转为0
*
* @param object 要转换的对象
* @return 转换后的值
*/
public static long toLong(Object object) {
return toLong(object, 0);
}
/**
* 将对象转为long类型,如果对象无法转换,将返回默认值
*
* @param object 要转换的对象
* @param defaultValue 默认值
* @return 转换后的值
*/
public static long toLong(Object object, long defaultValue) {
if (object instanceof Number) {
return ((Number) object).longValue();
}
if (isInt(object)) {
return Long.parseLong(object.toString());
}
if (isDouble(object)) {
return (long) Double.parseDouble(object.toString());
}
return defaultValue;
}
/**
* 对象是否为true
*
* @param obj
* @return
*/
public static boolean isTrue(Object obj) {
return "true".equals(String.valueOf(obj));
}
public static final String REG_EXP_DATE = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$";
/**
* 自动解析多种格式的时间字符串为时间对象<br>
* 支持格式为:yyyy-MM-dd HH:mm:ss 支持多种分隔符,以及多种日期精度。 如yyyy年MM月。 HH时mm分ss秒
*
* @param dateString 时间字符串 <br>
* @return 格式正确则返回对应的java.util.Date对象 格式错误返回null
*/
public static Date formatUnknownString2Date(String dateString) {
try {
if (StringUtils.isEmpty(dateString)) {
return null;
}
dateString = dateString.replace("T", " ");
String hms = "00:00:00";
dateString = dateString.trim();
if (dateString.contains(" ")) {
// 截取时分秒
hms = dateString.substring(dateString.indexOf(" ") + 1);
// 重置日期
dateString = dateString.substring(0, dateString.indexOf(" "));
// 多中分隔符的支持
hms = hms.replace(":", ":");
hms = hms.replace("时", ":");
hms = hms.replace("分", ":");
hms = hms.replace("秒", ":");
hms = hms.replace("-", ":");
hms = hms.replace("-", ":");
// 时间不同精确度的支持
if (hms.endsWith(":")) {
hms = hms.substring(0, hms.length() - 1);
}
if (hms.split(":").length == 1) {
hms += ":00:00";
}
if (hms.split(":").length == 2) {
hms += ":00";
}
}
String[] hmsarr = hms.split(":");
// 不同日期分隔符的支持
dateString = dateString.replace(".", "-");
dateString = dateString.replace("/", "-");
dateString = dateString.replace("-", "-");
dateString = dateString.replace("年", "-");
dateString = dateString.replace("月", "-");
dateString = dateString.replace("日", "");
// 切割年月日
String yearStr, monthStr, dateStr;
// 截取日期
String[] ymd = dateString.split("-");
// 判断日期精确度
yearStr = ymd[0];
monthStr = ymd.length > 1 ? ymd[1] : "";
dateStr = ymd.length > 2 ? ymd[2] : "";
monthStr = monthStr == "" ? Integer.toString(1) : monthStr;
dateStr = dateStr == "" ? Integer.toString(1) : dateStr;
String dtr = (yearStr + "-" + monthStr + "-" + dateStr + " " + hms);
if (!dtr.matches(REG_EXP_DATE)) {
return null;
}
// 返回日期
return new DateTime(Integer.parseInt(yearStr.trim()), Integer.parseInt(monthStr.trim()), Integer.parseInt(dateStr.trim()), Integer.parseInt(hmsarr[0].trim()), Integer.parseInt(hmsarr[1].trim()), Integer.parseInt(hmsarr[2].trim()), 0).toDate();
} catch (Exception e) {
return null;
}
}
/**
* 格式化日期时间
*
* @param date Date对象
* @param pattern 模式
* @return 格式化后的日期时间字符串
*/
public static String format(Date date, String pattern) {
if (date == null) {
return "";
}
return new DateTime(date).toString(pattern);
}
}