diff --git a/source.html b/source.html index 744f0cd7b..d288c6ed6 100644 --- a/source.html +++ b/source.html @@ -197,7 +197,7 @@ @Id @GeneratedValue @Column(comment = "UUID") - private String seqid = ""; //UUID + private String loginid = ""; //UUID @Column(updatable = false, comment = "C端用户ID") private long userid; //C端用户ID @@ -252,7 +252,135 @@ } } -
以上范例是用户登陆记录的分表分库策略,一年一个库,一个库中365张表,每天一个表。由于分表是根据记录的创建时间作为策略分表,因此策略接口中的public String getTable(String table, Serializable primary) 方法不能实现,因为根据主键id无法确定到数据库表。
+以上范例是用户登陆记录的分表分库策略,一年一个库,一个库中365张表,每天一个表。由于分表是根据记录的创建时间作为策略分表,因此策略接口中的public String getTable(String table, Serializable primary) 方法无法实现,根据主键id无法判断出数据所在的数据库表。若有根据ID获取单条记录的需求,则需要变更loginid的生成规则,可以改成UUID+createtime,如下:
+@DistributeTable(strategy = LoginRecord.TableStrategy.class)
+public class LoginRecord extends BaseEntity {
+
+ @Id
+ @Column(comment = "主键ID; 值=UUID+create36time")
+ private String loginid = ""; //主键ID; 值=UUID+create36time
+
+ @Column(updatable = false, comment = "C端用户ID")
+ private long userid; //C端用户ID
+
+ @Column(updatable = false, comment = "登录网络类型; wifi/4g/3g")
+ private String netmode = ""; //登录网络类型; wifi/4g/3g
+
+ @Column(updatable = false, comment = "APP版本信息")
+ private String appversion = ""; //APP版本信息
+
+ @Column(updatable = false, comment = "APP操作系统信息")
+ private String appos = ""; //APP操作系统信息
+
+ @Column(updatable = false, comment = "登录时客户端信息")
+ private String loginagent = ""; //登录时客户端信息
+
+ @Column(updatable = false, comment = "登录时的IP")
+ private String loginaddr = ""; //登录时的IP
+
+ @Column(updatable = false, comment = "创建时间")
+ private long createtime; //创建时间
+
+ /** 以下省略getter setter方法 */
+
+
+ //创建对象
+ public static void main(String[] args) throws Throwable {
+ LoginRecord record = new LoginRecord();
+ long now = System.currentTimeMillis();
+ record.setCreatetime(now); //设置创建时间
+ String create36time = Long.toString(now, 36); //时间的36进制
+ if (create36time.length() < 9) create36time = "0" + create36time; //当前时间值的36进制只可能是8位或9位,不足9位填充0
+ record.setLoginid(Utility.uuid() + create36time); //主键的生成规则
+ //.... 填充其他字段
+ source.insert(record);
+ }
+
+ public static class TableStrategy implements DistributeTableStrategy<LoginRecord> {
+
+ private static final String dayformat = "%1$tY%1$tm%1$td";
+
+ private static final String yearformat = "%1$tY";
+
+ //过滤查询时调用本方法
+ @Override
+ public String getTable(String table, FilterNode node) {
+ Serializable day = node.findValue("#day");
+ if (day != null) getTable(table, (Integer) day, 0L); //存在#day参数则直接使用day值
+ Serializable time = node.findValue("#createtime"); //存在createtime则使用最小时间,且createtime的范围必须在一天内,因为本表以天为单位建表
+ return getTable(table, 0, (time == null ? 0L : (time instanceof Range ? ((Range.LongRange) time).getMin() : (Long) time)));
+ }
+
+ //创建或单个查询时调用本方法
+ @Override
+ public String getTable(String table, LoginRecord bean) {
+ return getTable(table, 0, bean.getCreatetime());
+ }
+
+ //根据主键ID查询单个记录时调用本方法
+ @Override
+ public String getTable(String table, Serializable primary) {
+ String loginid = (String) primary;
+ String create36time = loginid.substring(loginid.length() - 9); //固定最后9位为创建时间的36进制值
+ return getTable(table, 0, Long.parseLong(create36time, 36));
+ }
+
+ private String getTable(String table, int day, long createtime) {
+ int pos = table.indexOf('.');
+ String year = (day > 0 ? "" + day / 10000 : String.format(yearformat, createtime));
+ return "platf_login_" + year + "." + table.substring(pos + 1) + "_" + (day > 0 ? day : String.format(dayformat, createtime));
+ }
+ }
+}
+ 如上范例,若需要分表策略的三个接口均得到实现,需要对主键ID的生成规则进行一定的设计。并不是所有的数据表都需要进行全量查询或单个记录查询,开发人员可根据使用场景来设计分表策略和主键生成规则。事实上用户登录记录很少场景需要查询单个记录的, 但是常见的场景是查询单个用户的登录列表。上面的范例无法满足查询单个用户的登录信息需求,而分表策略又只能根据一种规则生成,因此需要按用户维度存在另外一张表中。
+@DistributeTable(strategy = LoginUserRecord.TableStrategy.class)
+public class LoginUserRecord extends BaseEntity {
+
+ @Id
+ @GeneratedValue
+ @Column(comment = "UUID")
+ private String seqid = ""; //UUID
+
+ @Column(updatable = false, comment = "C端用户ID")
+ private long userid; //C端用户ID
+
+ @Column(comment = "LoginRecord主键")
+ private String loginid = ""; //LoginRecord主键
+
+ @Column(updatable = false, comment = "创建时间")
+ private long createtime; //创建时间
+
+ /** 以下省略getter setter方法 */
+
+ public static class TableStrategy implements DistributeTableStrategy<LoginUserRecord> {
+
+ @Override
+ public String getTable(String table, LoginUserRecord bean) {
+ return getTable(table, bean.getUserid());
+ }
+
+ @Override
+ public String getTable(String table, FilterNode node) {
+ Serializable id = node.findValue("userid");
+ if (id != null) return getTable(table, id);
+ return getHashTable(table, (Integer) node.findValue("#hash"));
+ }
+
+ @Override
+ public String getTable(String table, Serializable userid) {
+ return getHashTable(table, (int) (((Long) userid) % 100));
+ }
+
+ private String getHashTable(String table, int hash) {
+ int pos = table.indexOf('.');
+ return "platf_login." + table.substring(pos + 1) + "_" + (hash > 9 ? hash : ("0" + hash));
+ }
+
+ }
+}
+ 如上,表LoginUserRecord只存储用户ID与登录信息ID的关联关系,以用户ID取模100进行hash存储,获取用户登录列表时,先查询LoginUserRecord一页的数据,再根据loginid查询LoginRecord实体。常见的分表策略是时间和主键hash,例如用户信息表采用主键hash分表:
@DistributeTable(strategy = UserDetail.TableStrategy.class)
public class UserDetail extends BaseEntity {
@@ -304,7 +432,7 @@
}
}
- 如上,用户表以userid取模100进行hash分表,若需要提供根据手机号查询单个用户信息,则需要另外存在一个用户ID对应手机号码的关系表,同样可以以手机号后两位数字为hash存储。
CacheSource同Memcached类似,像一个带有过期功能地Map容器,存放key-value数据。常见的使用场景就是存放HTTP的Session信息。Redkale把用户会话信息数据当做业务数据处理,而不是接入层的数据。WebSocket的连接态数据也是用CacheSource存储。key为WebSocket的groupid,value为WebSocket服务端节点的IP地址列表。