├── .gitignore
├── README.MD
├── pom.xml
└── src
├── main
├── java
│ └── me
│ │ └── j360
│ │ └── rad
│ │ ├── action
│ │ ├── ActionCenter.java
│ │ ├── ActionCenterDelegate.java
│ │ ├── ActionCenterFactory.java
│ │ └── ActionConfig.java
│ │ ├── core
│ │ └── ActionTemplate.java
│ │ ├── exception
│ │ └── ActionException.java
│ │ ├── model
│ │ ├── Action.java
│ │ └── ActionTarget.java
│ │ ├── redis
│ │ └── RedisActionCenter.java
│ │ ├── spring
│ │ └── ActionCenterFactoryBean.java
│ │ └── util
│ │ └── NamedThreadFactory.java
└── resources
│ └── applicationContext.xml
└── test
├── java
└── me
│ └── j360
│ └── rad
│ └── test
│ └── ActionTest.java
└── logback-test.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jar
2 | *.war
3 | *~
4 | *.class
5 | *.lock
6 | *.DS_Store
7 | *.swp
8 | *.out
9 | target/
10 | *.iml
11 | *.ipr
12 | *.iws
13 | .settings/
14 | .classpath
15 | .project
16 | .metadata/
17 | .idea/
18 | logs/
19 |
20 | dependency-reduced-pom.xml
21 | aixforce-search/data
22 | *.rdb
23 | db/
24 | *.versionsBackup
25 | .flattened-pom.xml
26 |
27 | node_modules/
28 | sass/
29 | package.json
30 | Gruntfile.js
31 | config.js
32 | .sass-cache/
33 |
34 |
35 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | j360-redis-async-db
2 | ==============
3 |
4 | j360系列 - 缓存异步写数据库的框架
5 |
6 |
7 | ===v1.0功能实现===
8 | - 针对统计类的接口缓存(pv、uv等)信息后,需要异步将数据同步到持久层,先想几天再码代码
9 |
10 |
11 | ##相关前置说明##
12 | - 依赖Spring配置
13 | - 依赖redis读写接口
14 | - 需要另外存储redis对象(key、value)
15 | - 写数据库默认mysql(未来支持mongodb、es等)
16 |
17 |
18 | ##异步写策略##
19 | - 1.达到方法调用次数增量(可配置,默认100),触发同步数据库事件
20 | - 2.超过方法调用的时间增量(可配置,默认30s),触发同步数据库事件
21 | - 3.方法调用后会补一条异步触发消息(可配置,默认30s),满足条件(1.2未执行)触发同步数据库事件
22 |
23 | ##同步数据库##
24 | - 1.单条数据同步:立即执行
25 | - 2.批量执行同步:按照以下策略
26 |
27 | ##批量同步数据库策略##
28 | - 1.时间增量(可配置,默认3s)
29 | - 2.内容增量(可配置,默认1000条,请根据数据库性能配置)
30 |
31 | ##分布式支持##
32 | - 1.批量更新:单个jvm使用原子类锁进行队列执行
33 | - 2.分布式环境默认使用单条数据同步,批量同步需要依赖分布式队列框架(activemq、rabbitmq、kafka等)进行id分片策略同步(v2.0支持)
34 | - 3.id分片下的无状态集群需要抢分布式锁执行同步功能(redis、zk)
35 |
36 |
37 | ##简单版本定时策略##
38 | - 使用异步写3,通过队列定时30S后消费,消费时判断上一次同步时间是否超出30S,否则消费丢弃
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.j360.rad
8 | j360-redis-async-db
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 1.14.8
13 | 18.0
14 | 1.4.2.RELEASE
15 | 4.1.6.RELEASE
16 | 1.1.2
17 |
18 |
19 |
20 | org.projectlombok
21 | lombok
22 | ${lombok.version}
23 |
24 |
25 | com.google.guava
26 | guava
27 | ${guava.version}
28 |
29 |
30 |
31 | ch.qos.logback
32 | logback-classic
33 | ${logback.version}
34 |
35 |
36 | ch.qos.logback
37 | logback-core
38 | ${logback.version}
39 |
40 |
41 | ch.qos.logback
42 | logback-access
43 | ${logback.version}
44 |
45 |
46 |
47 |
48 | junit
49 | junit
50 | 4.11
51 | test
52 |
53 |
54 | org.springframework
55 | spring-test
56 | 4.1.6.RELEASE
57 | test
58 |
59 |
60 |
61 | org.springframework
62 | spring-tx
63 | ${spring.version}
64 |
65 |
66 | org.springframework
67 | spring-web
68 | ${spring.version}
69 |
70 |
71 | org.springframework
72 | spring-webmvc
73 | ${spring.version}
74 |
75 |
76 | org.springframework
77 | spring-core
78 | ${spring.version}
79 |
80 |
81 | org.springframework
82 | spring-expression
83 | ${spring.version}
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/action/ActionCenter.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.action;
2 |
3 | import me.j360.rad.model.Action;
4 | import me.j360.rad.model.ActionTarget;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Package: me.j360.rad.action
10 | * User: min_xu
11 | * Date: 16/4/13 下午4:10
12 | * 说明:
13 | */
14 | public interface ActionCenter {
15 |
16 | public void writeToCache(ActionTarget actionTarget);
17 | public void writeToDatabase(Action action);
18 |
19 | public void writeToDatabase(List actions);
20 |
21 | public ActionTarget getActionTarget(long targetId);
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/action/ActionCenterDelegate.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.action;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import me.j360.rad.model.Action;
6 | import me.j360.rad.model.ActionTarget;
7 | import me.j360.rad.util.NamedThreadFactory;
8 |
9 | import java.util.Date;
10 | import java.util.List;
11 | import java.util.concurrent.Executors;
12 | import java.util.concurrent.ScheduledExecutorService;
13 | import java.util.concurrent.ScheduledFuture;
14 | import java.util.concurrent.TimeUnit;
15 | import java.util.concurrent.atomic.AtomicBoolean;
16 |
17 | /**
18 | * Package: me.j360.rad.action
19 | * User: min_xu
20 | * Date: 16/4/13 下午4:12
21 | * 说明:
22 | */
23 | public class ActionCenterDelegate implements ActionCenter{
24 |
25 | @Setter
26 | @Getter
27 | private ActionCenter actionCenter;
28 |
29 | private ScheduledExecutorService executor;
30 |
31 | private ScheduledFuture scheduledFuture;
32 |
33 | private AtomicBoolean flushing = new AtomicBoolean(false);
34 |
35 | public ActionCenterDelegate(){
36 |
37 | }
38 |
39 | //入口
40 | public void call(Action action) {
41 | long targetId = 0;
42 | ActionTarget actionTarget = getActionTarget(targetId);
43 | //写入缓存新的数据
44 | writeToCache(actionTarget);
45 |
46 | boolean isWrite = false;
47 |
48 | if(actionTarget.getCount() == actionTarget.getNextCount()){
49 | //同步到数据库
50 | isWrite = true;
51 | writeToDatabase(action);
52 | }
53 | if(isWrite == false){
54 | if(new Date(actionTarget.getTimestamp()).after(new Date())){
55 | //同步到数据库
56 | isWrite = true;
57 | writeToDatabase(action);
58 | }
59 | }
60 |
61 | //补一个定时队列去判断是否有必要在下一个call之前检查
62 | schedulerCheck(action);
63 | }
64 |
65 | public void writeToCache(ActionTarget actionTarget) {
66 | actionCenter.writeToCache(actionTarget);
67 | }
68 |
69 | public void writeToDatabase(Action action) {
70 | actionCenter.writeToDatabase(action);
71 | }
72 |
73 | @Override
74 | public void writeToDatabase(List actions) {
75 |
76 | }
77 |
78 | @Override
79 | public ActionTarget getActionTarget(long targetId) {
80 | return actionCenter.getActionTarget(targetId);
81 | }
82 |
83 | private void schedulerCheck(final Action action){
84 | //
85 | executor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("LazyAction"));
86 | scheduledFuture = executor.scheduleWithFixedDelay(new Runnable() {
87 | @Override
88 | public void run() {
89 | try {
90 | if (flushing.compareAndSet(false, true)) {
91 | //
92 | writeToDatabase(action);
93 | }
94 | } catch (Throwable t) {
95 |
96 | }
97 | }
98 | }, 30, 30, TimeUnit.SECONDS);
99 |
100 |
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/action/ActionCenterFactory.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.action;
2 |
3 | /**
4 | * Package: me.j360.rad.action
5 | * User: min_xu
6 | * Date: 16/4/13 下午4:09
7 | * 说明:
8 | */
9 | public class ActionCenterFactory {
10 |
11 | private ActionCenter actionCenter;
12 |
13 | public ActionCenter getActionCenter() {
14 | return actionCenter;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/action/ActionConfig.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.action;
2 |
3 | /**
4 | * Package: me.j360.rad.action
5 | * User: min_xu
6 | * Date: 16/4/13 下午4:11
7 | * 说明:异步写数据库的配置
8 | */
9 | public class ActionConfig {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/core/ActionTemplate.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.core;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import me.j360.rad.action.ActionCenter;
6 | import me.j360.rad.action.ActionCenterDelegate;
7 | import me.j360.rad.model.Action;
8 |
9 | /**
10 | * Package: me.j360.rad.core
11 | * User: min_xu
12 | * Date: 16/4/13 下午4:08
13 | * 说明:
14 | */
15 | public class ActionTemplate{
16 |
17 | @Getter
18 | @Setter
19 | private ActionCenterDelegate actionDelegate;
20 |
21 | public void call(Action action){
22 | //调用delegate
23 | actionDelegate.call(action);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/exception/ActionException.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.exception;
2 |
3 | /**
4 | * Package: me.j360.rad.exception
5 | * User: min_xu
6 | * Date: 16/4/13 下午4:12
7 | * 说明:
8 | */
9 | public class ActionException extends RuntimeException{
10 |
11 | public ActionException(){
12 | super();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/model/Action.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | /**
7 | * Package: me.j360.rad.model
8 | * User: min_xu
9 | * Date: 16/4/13 下午4:03
10 | * 说明:
11 | */
12 | public class Action {
13 | @Getter
14 | @Setter
15 | private long actionId;
16 | @Getter
17 | @Setter
18 | private String table;
19 | @Getter
20 | @Setter
21 | private String field;
22 | @Getter
23 | @Setter
24 | private String targetId;
25 | @Getter
26 | @Setter
27 | private long number;
28 | @Getter
29 | @Setter
30 | private String timestamp;
31 | @Getter
32 | @Setter
33 | private long delay;
34 | @Getter
35 | @Setter
36 | private String destimate;
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/model/ActionTarget.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | /**
7 | * Package: me.j360.rad.model
8 | * User: min_xu
9 | * Date: 16/4/13 下午4:53
10 | * 说明:
11 | */
12 | public class ActionTarget {
13 |
14 | @Getter
15 | @Setter
16 | private long targetId;
17 | @Getter
18 | @Setter
19 | private String timestamp;
20 | @Getter
21 | @Setter
22 | private long count;
23 | @Getter
24 | @Setter
25 | private long nextCount;
26 | @Getter
27 | @Setter
28 | private long data;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/redis/RedisActionCenter.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.redis;
2 |
3 | import me.j360.rad.action.ActionCenter;
4 | import me.j360.rad.model.Action;
5 | import me.j360.rad.model.ActionTarget;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * Package: me.j360.rad.redis
11 | * User: min_xu
12 | * Date: 16/4/13 下午4:25
13 | * 说明:
14 | */
15 | public class RedisActionCenter implements ActionCenter {
16 |
17 |
18 | public void writeToCache(ActionTarget actionTarget) {
19 |
20 | }
21 |
22 | @Override
23 | public void writeToDatabase(Action action) {
24 | //将单条数据写到数据库
25 |
26 | }
27 |
28 | @Override
29 | public void writeToDatabase(List actions) {
30 | //批量将数据写到数据库
31 |
32 | }
33 |
34 | @Override
35 | public ActionTarget getActionTarget(long targetId) {
36 |
37 | return new ActionTarget();
38 | }
39 |
40 |
41 | private boolean checkDelay(){
42 | return true;
43 | }
44 |
45 | private void asyncCall(){
46 |
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/spring/ActionCenterFactoryBean.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.spring;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import me.j360.rad.action.ActionCenter;
6 | import me.j360.rad.action.ActionCenterFactory;
7 | import org.springframework.beans.factory.DisposableBean;
8 | import org.springframework.beans.factory.FactoryBean;
9 | import org.springframework.beans.factory.InitializingBean;
10 |
11 | /**
12 | * Package: me.j360.rad.spring
13 | * User: min_xu
14 | * Date: 16/4/13 下午7:18
15 | * 说明:
16 | */
17 | public class ActionCenterFactoryBean implements FactoryBean,
18 | InitializingBean, DisposableBean {
19 |
20 | @Getter
21 | @Setter
22 | private ActionCenter actionCenter;
23 |
24 | public void destroy() throws Exception {
25 |
26 | }
27 |
28 | public ActionCenter getObject() throws Exception {
29 | return actionCenter;
30 | }
31 |
32 | public Class> getObjectType() {
33 | return actionCenter.getClass();
34 | }
35 |
36 | public boolean isSingleton() {
37 | return false;
38 | }
39 |
40 | public void afterPropertiesSet() throws Exception {
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/me/j360/rad/util/NamedThreadFactory.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.util;
2 |
3 | import java.util.concurrent.ThreadFactory;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | /**
7 | * Package: me.j360.rad.util
8 | * User: min_xu
9 | * Date: 16/4/13 下午5:22
10 | * 说明:
11 | */
12 | public class NamedThreadFactory implements ThreadFactory {
13 | private static final AtomicInteger POOL_SEQ = new AtomicInteger(1);
14 |
15 | private final AtomicInteger mThreadNum = new AtomicInteger(1);
16 |
17 | private final String mPrefix;
18 |
19 | private final boolean mDaemo;
20 |
21 | private final ThreadGroup mGroup;
22 |
23 | public NamedThreadFactory() {
24 | this("pool-" + POOL_SEQ.getAndIncrement(), false);
25 | }
26 |
27 | public NamedThreadFactory(String prefix) {
28 | this(prefix, false);
29 | }
30 |
31 | public NamedThreadFactory(String prefix, boolean daemo) {
32 | mPrefix = prefix + "-thread-";
33 | mDaemo = daemo;
34 | SecurityManager s = System.getSecurityManager();
35 | mGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
36 | }
37 |
38 | public Thread newThread(Runnable runnable) {
39 | String name = mPrefix + mThreadNum.getAndIncrement();
40 | Thread ret = new Thread(mGroup, runnable, name, 0);
41 | ret.setDaemon(mDaemo);
42 | return ret;
43 | }
44 |
45 | public ThreadGroup getThreadGroup() {
46 | return mGroup;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/resources/applicationContext.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/test/java/me/j360/rad/test/ActionTest.java:
--------------------------------------------------------------------------------
1 | package me.j360.rad.test;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import me.j360.rad.core.ActionTemplate;
5 | import me.j360.rad.model.Action;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.test.context.ActiveProfiles;
10 | import org.springframework.test.context.ContextConfiguration;
11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 | import org.springframework.test.context.transaction.TransactionConfiguration;
13 |
14 | /**
15 | * Package: me.j360.rad.test
16 | * User: min_xu
17 | * Date: 16/4/13 下午7:29
18 | * 说明:
19 | */
20 | @Slf4j
21 | @ActiveProfiles("dev")
22 | @RunWith(SpringJUnit4ClassRunner.class)
23 | @TransactionConfiguration(defaultRollback=false)
24 | @ContextConfiguration(locations = {"classpath:/applicationContext.xml"})
25 | public class ActionTest {
26 |
27 | @Autowired
28 | private ActionTemplate actionTemplate;
29 |
30 | @Test
31 | public void call(){
32 | Action action = new Action();
33 | actionTemplate.call(action);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------