Redis中不仅保存着Clickhouse计算出来的预聚合
数据,也保存Flink计算结果,例如,每5分钟统计一次近1小时的登录数据
。
原创大约 17 分钟
Redis中不仅保存着Clickhouse计算出来的预聚合
数据,也保存Flink计算结果,例如,每5分钟统计一次近1小时的登录数据
。
类似近1小时内用户的登录次数多于3次
这样的风控指标,本质上是一种风控关系表达式
,它由左变量
、关系运算符
和右变量
(或阈值
)组成,如果把它用另外一种方式展现出来就是这样。
对于这种风控指标的计算来说,真正的难点不在于计算本身,而在于 如何快速且准确地取得指定时间片的数据
。
环境和脚本
> go get -u github.com/go-redis/redis/v8
> go get -u github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi
安装好Docker,然后执行下面的脚本。
# 默认配置
> docker run -d -p 6379:6379 redis:latest redis-server
# 自定义配置
> docker run -d -p 6379:6379 \
--name redis \
-v /home/work/volumes/redis/redis.conf:/etc/redis.conf \
-v /home/work/volumes/redis/data:/data \
--privileged=true \
redis:latest redis-server /etc/redis.conf
事实上,Redis是可以作为消息队列来用的。
先创建属性配置类Configuration
。
package com.jedis.mq;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 属性配置类
*
*/
public class Configuration extends Properties {
private static final long serialVersionUID = 50440463580273222L;
private static Configuration instance = null;
public static synchronized Configuration getInstance() {
if (instance == null) {
instance = new Configuration();
}
return instance;
}
public String getProperty(String key, String defaultValue) {
String val = getProperty(key);
return (val == null || val.isEmpty()) ? defaultValue : val;
}
public String getString(String name, String defaultValue) {
return this.getProperty(name, defaultValue);
}
public int getInt(String name, int defaultValue) {
String val = this.getProperty(name);
return (val == null || val.isEmpty()) ? defaultValue : Integer.parseInt(val);
}
public long getLong(String name, long defaultValue) {
String val = this.getProperty(name);
return (val == null || val.isEmpty()) ? defaultValue : Integer.parseInt(val);
}
public float getFloat(String name, float defaultValue) {
String val = this.getProperty(name);
return (val == null || val.isEmpty()) ? defaultValue : Float.parseFloat(val);
}
public double getDouble(String name, double defaultValue) {
String val = this.getProperty(name);
return (val == null || val.isEmpty()) ? defaultValue : Double.parseDouble(val);
}
public byte getByte(String name, byte defaultValue) {
String val = this.getProperty(name);
return (val == null || val.isEmpty()) ? defaultValue : Byte.parseByte(val);
}
public Configuration() {
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("config.xml");
try {
this.loadFromXML(in);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.jedis;
import java.util.HashMap;
import java.util.Map;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Redis工具类,用于获取RedisPool. You shouldn't use the same instance from different
* threads because you'll have strange errors. And sometimes creating lots of
* Jedis instances is not good enough because it means lots of sockets and
* connections, which leads to strange errors as well. A single Jedis instance
* is not threadsafe! To avoid these problems, you should use JedisPool, which
* is a threadsafe pool of network connections. This way you can overcome those
* strange errors and achieve great performance. To use it, init a pool:
* JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost"); You can
* store the pool somewhere statically, it is thread-safe. JedisPoolConfig
* includes a number of helpful Redis-specific connection pooling defaults. For
* example, Jedis with JedisPoolConfig will close a connection after 300 seconds
* if it has not been returned.
*/
@SuppressWarnings("deprecation")
public class JedisUtil {
private static Map<String, JedisPool> maps = new HashMap<String, JedisPool>();
/**
* 私有构造器
*
*/
private JedisUtil() {
}
/**
* 获取连接池
*
* @return 连接池实例
*/
private static JedisPool getPool(String ip, int port) {
String key = ip + ":" + port;
JedisPool pool = null;
if (!maps.containsKey(key)) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxWaitMillis(1000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
try {
/**
* 如果你遇到 java.net.SocketTimeoutException: Read timed out
* exception的异常信息 请尝试在构造JedisPool的时候设置自己的超时值.
* JedisPool默认的超时时间是2秒(单位毫秒)
*/
pool = new JedisPool(config, ip, port, 30);
maps.put(key, pool);
} catch (Exception e) {
e.printStackTrace();
}
} else {
pool = maps.get(key);
}
return pool;
}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载
*/
private static class RedisUtilHolder {
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static JedisUtil instance = new JedisUtil();
}
/**
* 当getInstance方法第一次被调用的时候,它第一次读取RedisUtilHolder.instance,导致RedisUtilHolder类得到初始化
* 而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建RedisUtil的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性
* 这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本
*
*/
public static JedisUtil getInstance() {
return RedisUtilHolder.instance;
}
/**
* 获取Redis实例
*
* @return Redis工具类实例
*/
public Jedis getJedis(String ip, int port) {
Jedis jedis = null;
int count = 0;
do {
try {
jedis = getPool(ip, port).getResource();
} catch (Exception e) {
// 销毁对象
getPool(ip, port).returnBrokenResource(jedis);
}
count++;
} while (jedis == null && count < 10);
return jedis;
}
/**
* 释放redis实例到连接池
*
* @param jedis redis实例
*/
public void closeJedis(Jedis jedis, String ip, int port) {
if (jedis != null) {
getPool(ip, port).returnResource(jedis);
}
}
}
MySQL固然强大,但在某些需求场景下却难于使用。
例如,如果用户通过验证码登录,那么单独创建一张存储手机号和验证码的表,然后用完之后或者隔一段时间验证码失效之后再把它删除。
再比如,在社交类型的应用中,最重要的两类数据就是FOLLOW
和BE FOLLOWED
,它们本质上就是一个用户名加上一个不断变化的粉丝数量而已。
类似这种手机号 + 验证码
和用户名 + 粉丝数量
的数据,在软件开发中有一个专有名词,称之为键值对
,意思就是一个键(用key表示)
跟着一个数值(用value表示)
,形式是:key:value
。
以下代码是早期学习Redis时所做的注释和记录,那时的Redis还是4.x版本。
虽然目前Redis已经更新到了7.x版本,有些数据类型也发生了变化,但至少没有根本性的变化,而且其中大多数的数据类型还是可用的。