├── .gitignore ├── README.md ├── code_count.sh ├── common ├── pom.xml └── src │ ├── main │ ├── assembly │ │ ├── package-common-jar.xml │ │ └── shared-jar-pom.xml │ └── java │ │ └── com │ │ └── sumory │ │ └── gru │ │ └── common │ │ ├── config │ │ └── Config.java │ │ ├── domain │ │ └── StatObject.java │ │ ├── id │ │ ├── BaseN.java │ │ └── IdWorker.java │ │ ├── utils │ │ ├── BitSetUtil.java │ │ ├── CollectionUtils.java │ │ ├── DataCodec.java │ │ ├── DateUtil.java │ │ ├── IdUtil.java │ │ ├── RedisUtil.java │ │ └── TokenUtil.java │ │ └── zk │ │ ├── NotifyListener.java │ │ ├── ZkClientWatcher.java │ │ └── ZkNode.java │ └── test │ └── java │ └── com │ └── sumory │ └── gru │ └── common │ └── utils │ ├── DataCodecTest.java │ ├── DateUtilsTest.java │ └── IdUtilsTest.java ├── docs ├── cluster_install.md ├── dashboard-client.png ├── dashboard-monitor.png ├── gru_api.md ├── 架构草图0.2.jpg └── 登陆策略.md ├── idgen ├── pom.xml └── src │ └── main │ ├── assembly │ ├── package-idgen-jar.xml │ └── shared-jar-pom.xml │ ├── java │ └── com │ │ └── sumory │ │ └── gru │ │ └── idgen │ │ ├── id │ │ ├── IdUtils.java │ │ └── IdWorker.java │ │ ├── main │ │ └── IdGenMain.java │ │ └── service │ │ ├── IdService.java │ │ └── impl │ │ └── IdServiceImpl.java │ └── resources │ ├── bin │ ├── dump.sh │ ├── start.sh │ └── stop.sh │ ├── profiles │ ├── dev │ │ ├── dubbo.properties │ │ ├── log4j2.xml │ │ └── system.properties │ └── test1 │ │ ├── dubbo.properties │ │ ├── log4j2.xml │ │ └── system.properties │ └── spring │ ├── applicationContext-provider-idgen.xml │ ├── applicationContext.xml │ └── dubbo.xsd ├── minions ├── .gitignore ├── Makefile ├── README.md ├── app.js ├── config │ ├── dev.js │ ├── index.js │ ├── prod.js │ └── test.js ├── lib │ ├── log.js │ ├── redisUtils.js │ ├── utils.js │ └── zkUtils.js ├── models │ └── adminModel.js ├── package.json ├── public │ ├── css │ │ ├── animate.min.css │ │ ├── bootstrap.css │ │ └── minions.css │ ├── img │ │ └── gru.png │ ├── js │ │ ├── gru │ │ │ └── monitor.js │ │ ├── jquery-2.1.3.min.js │ │ ├── moment.min.js │ │ └── socket.io │ │ │ └── socket.io.js │ └── semantic │ │ ├── components │ │ ├── accordion.css │ │ ├── accordion.js │ │ ├── accordion.min.css │ │ ├── accordion.min.js │ │ ├── ad.css │ │ ├── ad.min.css │ │ ├── api.js │ │ ├── api.min.js │ │ ├── breadcrumb.css │ │ ├── breadcrumb.min.css │ │ ├── button.css │ │ ├── button.min.css │ │ ├── card.css │ │ ├── card.min.css │ │ ├── checkbox.css │ │ ├── checkbox.js │ │ ├── checkbox.min.css │ │ ├── checkbox.min.js │ │ ├── comment.css │ │ ├── comment.min.css │ │ ├── dimmer.css │ │ ├── dimmer.js │ │ ├── dimmer.min.css │ │ ├── dimmer.min.js │ │ ├── divider.css │ │ ├── divider.min.css │ │ ├── dropdown.css │ │ ├── dropdown.js │ │ ├── dropdown.min.css │ │ ├── dropdown.min.js │ │ ├── feed.css │ │ ├── feed.min.css │ │ ├── flag.css │ │ ├── flag.min.css │ │ ├── form.css │ │ ├── form.js │ │ ├── form.min.css │ │ ├── form.min.js │ │ ├── grid.css │ │ ├── grid.min.css │ │ ├── header.css │ │ ├── header.min.css │ │ ├── icon.css │ │ ├── icon.min.css │ │ ├── image.css │ │ ├── image.min.css │ │ ├── input.css │ │ ├── input.min.css │ │ ├── item.css │ │ ├── item.min.css │ │ ├── label.css │ │ ├── label.min.css │ │ ├── list.css │ │ ├── list.min.css │ │ ├── loader.css │ │ ├── loader.min.css │ │ ├── menu.css │ │ ├── menu.min.css │ │ ├── message.css │ │ ├── message.min.css │ │ ├── modal.css │ │ ├── modal.js │ │ ├── modal.min.css │ │ ├── modal.min.js │ │ ├── nag.css │ │ ├── nag.js │ │ ├── nag.min.css │ │ ├── nag.min.js │ │ ├── popup.css │ │ ├── popup.js │ │ ├── popup.min.css │ │ ├── popup.min.js │ │ ├── progress.css │ │ ├── progress.js │ │ ├── progress.min.css │ │ ├── progress.min.js │ │ ├── rail.css │ │ ├── rail.min.css │ │ ├── rating.css │ │ ├── rating.js │ │ ├── rating.min.css │ │ ├── rating.min.js │ │ ├── reset.css │ │ ├── reset.min.css │ │ ├── reveal.css │ │ ├── reveal.min.css │ │ ├── search.css │ │ ├── search.js │ │ ├── search.min.css │ │ ├── search.min.js │ │ ├── segment.css │ │ ├── segment.min.css │ │ ├── shape.css │ │ ├── shape.js │ │ ├── shape.min.css │ │ ├── shape.min.js │ │ ├── sidebar.css │ │ ├── sidebar.js │ │ ├── sidebar.min.css │ │ ├── sidebar.min.js │ │ ├── site.css │ │ ├── site.js │ │ ├── site.min.css │ │ ├── site.min.js │ │ ├── state.js │ │ ├── state.min.js │ │ ├── statistic.css │ │ ├── statistic.min.css │ │ ├── step.css │ │ ├── step.min.css │ │ ├── sticky.css │ │ ├── sticky.js │ │ ├── sticky.min.css │ │ ├── sticky.min.js │ │ ├── tab.css │ │ ├── tab.js │ │ ├── tab.min.css │ │ ├── tab.min.js │ │ ├── table.css │ │ ├── table.min.css │ │ ├── transition.css │ │ ├── transition.js │ │ ├── transition.min.css │ │ ├── transition.min.js │ │ ├── video.css │ │ ├── video.js │ │ ├── video.min.css │ │ ├── video.min.js │ │ ├── visibility.js │ │ └── visibility.min.js │ │ ├── semantic.css │ │ ├── semantic.js │ │ ├── semantic.min.css │ │ ├── semantic.min.js │ │ └── themes │ │ ├── basic │ │ └── assets │ │ │ └── fonts │ │ │ ├── icons.eot │ │ │ ├── icons.svg │ │ │ ├── icons.ttf │ │ │ └── icons.woff │ │ └── default │ │ └── assets │ │ ├── fonts │ │ ├── icons.eot │ │ ├── icons.otf │ │ ├── icons.svg │ │ ├── icons.ttf │ │ ├── icons.woff │ │ └── icons.woff2 │ │ └── images │ │ └── flags.png ├── route.js ├── routes │ ├── mockRouter.js │ └── monitorRouter.js ├── start_prod.sh ├── start_test.sh ├── test │ ├── redis.js │ └── utils.js └── views │ ├── common │ ├── header.ejs │ ├── meta.ejs │ └── sidebar.ejs │ ├── error.ejs │ ├── index.ejs │ └── monitor.ejs ├── pom.xml ├── spear-client ├── docs │ └── nett-socket.io.bench.txt ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sumory │ │ │ └── gru │ │ │ └── spear │ │ │ └── client │ │ │ ├── Main.java │ │ │ ├── msg │ │ │ └── MsgMain.java │ │ │ └── pojo │ │ │ ├── ChatObject.java │ │ │ └── User.java │ └── resources │ │ ├── bin │ │ ├── start.sh │ │ └── start_msg.sh │ │ └── profiles │ │ └── test │ │ └── log4j.properties │ └── test │ └── java │ └── com │ └── sumory │ └── gru │ └── spear │ └── client │ └── Test.java ├── spear ├── docs │ ├── bench_1000clients.txt │ ├── bench_200clients.txt │ ├── bench_log.txt │ ├── nett-socket.io.bench.txt │ ├── performance.txt │ └── 选型.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sumory │ │ │ └── gru │ │ │ └── spear │ │ │ ├── SpearContext.java │ │ │ ├── domain │ │ │ ├── AuthObject.java │ │ │ ├── Client.java │ │ │ ├── CommonResult.java │ │ │ ├── Group.java │ │ │ ├── MsgObject.java │ │ │ ├── MsgType.java │ │ │ ├── ResultCode.java │ │ │ ├── SubscribeObject.java │ │ │ └── User.java │ │ │ ├── extention │ │ │ ├── IAck.java │ │ │ ├── LogAck.java │ │ │ ├── RabbitMQAck.java │ │ │ ├── ReceiverDataListener.java │ │ │ └── SenderDataListener.java │ │ │ ├── main │ │ │ └── SpearMain.java │ │ │ ├── message │ │ │ ├── BaseMessage.java │ │ │ └── StringMessage.java │ │ │ ├── monitor │ │ │ └── MonitorServer.java │ │ │ ├── server │ │ │ ├── ActionListener.java │ │ │ └── SpearServer.java │ │ │ ├── service │ │ │ ├── InnerIdService.java │ │ │ └── InnerStatService.java │ │ │ ├── task │ │ │ └── UserStat.java │ │ │ ├── thread │ │ │ ├── ExecutesManager.java │ │ │ ├── NameThreadFactory.java │ │ │ └── NamedThreadFactory.java │ │ │ ├── transport │ │ │ ├── IReceiver.java │ │ │ ├── ISender.java │ │ │ ├── inner │ │ │ │ ├── InnerReceiver.java │ │ │ │ └── InnerSender.java │ │ │ ├── redis │ │ │ │ ├── RedisListener.java │ │ │ │ ├── RedisReceiver.java │ │ │ │ └── RedisSender.java │ │ │ └── rocketmq │ │ │ │ ├── RocketMQReceiver.java │ │ │ │ └── RocketMQSender.java │ │ │ └── zk │ │ │ ├── SpearNotifyListener.java │ │ │ ├── SpearZkClientWatcher.java │ │ │ └── ZkUtil.java │ └── resources │ │ ├── bin │ │ └── start.sh │ │ ├── profiles │ │ ├── dev │ │ │ ├── dubbo.properties │ │ │ ├── log4j2.xml │ │ │ └── system.properties │ │ ├── test1 │ │ │ ├── dubbo.properties │ │ │ ├── log4j2.xml │ │ │ └── system.properties │ │ └── test2 │ │ │ ├── dubbo.properties │ │ │ ├── log4j2.xml │ │ │ └── system.properties │ │ └── spring │ │ ├── applicationContext-consumer-idgen.xml │ │ ├── applicationContext-consumer-stat.xml │ │ └── applicationContext.xml │ └── test │ └── java │ └── com │ └── sumory │ └── gru │ └── spear │ ├── Test.java │ ├── mq │ ├── Consumer.java │ └── Producer.java │ └── zk │ └── ZkTest.java ├── stat ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── assembly │ │ ├── package-stat-jar.xml │ │ └── shared-jar-pom.xml │ ├── java │ │ └── com │ │ │ └── sumory │ │ │ └── gru │ │ │ └── stat │ │ │ ├── context │ │ │ └── StatContext.java │ │ │ ├── main │ │ │ └── StatMain.java │ │ │ ├── redis │ │ │ └── RedisStore.java │ │ │ ├── service │ │ │ ├── StatService.java │ │ │ └── impl │ │ │ │ └── StatServiceImpl.java │ │ │ ├── servlet │ │ │ ├── StatServlet.java │ │ │ └── User.java │ │ │ └── zk │ │ │ ├── StatNotifyListener.java │ │ │ ├── StatZkClientWatcher.java │ │ │ └── ZkUtil.java │ └── resources │ │ ├── bin │ │ ├── dump.sh │ │ ├── start.sh │ │ └── stop.sh │ │ ├── profiles │ │ ├── dev │ │ │ ├── dubbo.properties │ │ │ ├── log4j2.xml │ │ │ └── system.properties │ │ ├── test1 │ │ │ ├── dubbo.properties │ │ │ ├── log4j2.xml │ │ │ └── system.properties │ │ └── test2 │ │ │ ├── dubbo.properties │ │ │ ├── log4j2.xml │ │ │ └── system.properties │ │ └── spring │ │ ├── applicationContext-provider-stat.xml │ │ ├── applicationContext.xml │ │ └── dubbo.xsd │ └── test │ └── java │ └── com │ └── sumory │ └── gru │ └── common │ └── utils │ └── RedisStoreTest.java └── ticket ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── sumory │ │ └── gru │ │ └── ticket │ │ ├── common │ │ ├── Node.java │ │ └── Shard.java │ │ ├── domain │ │ └── Ticket.java │ │ ├── main │ │ └── TicketMain.java │ │ ├── server │ │ ├── HttpHandler.java │ │ └── TicketServer.java │ │ └── zk │ │ ├── TicketNotifyListener.java │ │ ├── TicketZkClientWatcher.java │ │ └── ZkUtil.java └── resources │ ├── bin │ ├── start.sh │ └── stop.sh │ └── profiles │ ├── dev │ ├── log4j2.xml │ └── system.properties │ ├── test1 │ ├── log4j2.xml │ └── system.properties │ └── test2 │ ├── log4j2.xml │ └── system.properties └── test └── java └── com └── sumory └── gru └── ticket └── common └── test ├── ConsistentHash.java ├── ConsistentHashing.java ├── MockSpearNode.java ├── ShardTest.java └── ZooKeeperTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /test/ 2 | /out/ 3 | .svn 4 | .idea 5 | .DS_Store 6 | .project 7 | .metadata/ 8 | .settings/ 9 | .classpath 10 | target 11 | *.via 12 | *.tmp 13 | *.err 14 | *.log 15 | *.log.* 16 | *.iml 17 | .factorypath 18 | node_modules 19 | myconf 20 | trash 21 | src/main/resources/profiles/myself 22 | dump.rdb 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Gru 2 | 3 | **Gru**是一个长连接服务解决方案,可用于各种类型的实时交互应用。 4 | 5 | 6 | #### 目前主要模块 7 | 8 | - common 通用的一些代码、工具类等 9 | - idgen 消息id、某些业务id生成服务 10 | - ticket 11 | - 处于最前端,客户端首先请求该服务拿到ticket才能建立长连接 12 | - 目前实现了基于一致性hash的负载均衡,将客户端路由到不同的spear节点 13 | - 其它位于前端的策略,如准入机制等可在此模块扩展 14 | - stat 统计模块、业务模块 15 | - 目前实现了在线统计,数据存储在redis 16 | - 若还需要后端接入其他业务服务,可在此模块扩展 17 | - spear 长连接服务模块 18 | - 提供长连接接入的模块 19 | - 最小化模式部署时只需要此模块的一个实例即可 20 | - minions 监控模块 21 | - 目前可监控集群长连接服务spear节点数、每个节点的用户数等 22 | - 节点间管理、用户管理可在此模块扩展 23 | - spear-client 客户端模拟,压测示例代码 24 | - 示例项目: [gru-example](https://github.com/sumory/gru-example) 25 | - 以IM作为示例展示基于Gru构建实时应用 26 | - 支持群聊和单聊 27 | 28 | 29 | #### 特性 30 | 31 | - 支持单点部署和集群模式部署 32 | - 采用[socket.io](http://socket.io)协议 33 | - 各模块均支持水平扩展 34 | - 单节点可服务10W+以上长连接,具体为在不断发消息的情况下(1000条/秒),单长连接服务节点支持的稳定连接数量在10W+(8核16G) 35 | - 节点间通讯支持多种方式:进程内、redis、rocketmq 36 | 37 | 38 | #### 安装部署 39 | 40 | git clone https://github.com/sumory/gru.git /data/tmp/gru 41 | 42 | ##### 最小安装,单点部署 43 | 44 | 如果只想体验单节点的部署模式,不需要监控、负载均衡及其它业务服务,只需要部署一个spear节点即可。部署方式如下: 45 | 46 | ``` 47 | cd /data/tmp/gru 48 | mvn install #本地安装 49 | cd spear 50 | mvn clean package -Pdev #生成spear可运行包,使用dev的配置文件 51 | cd target/release #该目录下有所有spear运行需要的文件 52 | ll conf # 主要配置文件位于conf/system.properties下,默认以single模式运行,不依赖zookeeper/redis等 53 | sh bin/start.sh # 启动 54 | 55 | ``` 56 | 以上步骤完成后即可在本地的31001端口开启一个spear实例监听,等待客户端连接。 57 | 58 | 若想查看如何使用该spear实例,请移步[gru-example](https://github.com/sumory/gru-example),启动该项目,访问http://127.0.0.1:60000即可。 59 | 60 | 61 | ##### 集群方式部署 62 | 63 | 集群的安装配置较为复杂,详见[Gru集群安装配置](docs/cluster_install.md) 64 | 65 | 66 | #### 监控后台screenshots 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /code_count.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cloc . 4 | #cloc . --exclude-lang=Javascript,CSS 5 | -------------------------------------------------------------------------------- /common/src/main/assembly/package-common-jar.xml: -------------------------------------------------------------------------------- 1 | 5 | dist 6 | 7 | jar 8 | 9 | false 10 | 11 | 12 | ${project.build.directory}/classes 13 | 16 | 17 | 18 | *.xml 19 | 20 | / 21 | 22 | 23 | 24 | 31 | 32 | -------------------------------------------------------------------------------- /common/src/main/assembly/shared-jar-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | gru 6 | common 7 | gru-common-shared-jar 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.config; 2 | 3 | import java.io.IOException; 4 | import java.util.Enumeration; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Properties; 8 | 9 | /** 10 | * 配置 11 | * 12 | * @author sumory.wu 13 | * @date 2015年1月21日 上午10:41:42 14 | */ 15 | public class Config { 16 | private static Properties properties; 17 | private static volatile boolean isLoad; 18 | 19 | static { 20 | synchronized (Config.class) { 21 | load(); 22 | } 23 | } 24 | 25 | public static String get(String key) { 26 | if (!isLoad) 27 | load(); 28 | return properties.getProperty(key); 29 | } 30 | 31 | public static Map getConfig() { 32 | Map result = new HashMap<>(); 33 | if (!isLoad) 34 | load(); 35 | Enumeration e = (Enumeration) properties.propertyNames(); 36 | while (e.hasMoreElements()) { 37 | String k = e.nextElement(); 38 | result.put(k, get(k)); 39 | } 40 | return result; 41 | } 42 | 43 | private static void load() { 44 | properties = new Properties(); 45 | try { 46 | properties.load(Config.class.getClassLoader().getResourceAsStream("system.properties")); 47 | isLoad = true; 48 | } 49 | catch (IOException e) { 50 | System.out.println("can not load the properties"); 51 | e.printStackTrace(); 52 | System.exit(-1); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/domain/StatObject.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.BitSet; 5 | 6 | /** 7 | * 群组状态统计 8 | * 9 | * @author sumory.wu 10 | * @date 2015年4月20日 下午3:11:18 11 | */ 12 | public class StatObject implements Serializable { 13 | private static final long serialVersionUID = 1L; 14 | 15 | private boolean isEmpty;//bitSet是否为空,即是否有学生 16 | private long groupId; 17 | private int base;//bitset中所有值的基值,如base为5,bitset里存的是1,2,3,那么实际值则是6,7,8 18 | private String extra;//扩展数据 19 | private transient BitSet bitSet;//dubbo默认的hessian无法序列化BitSet,所以用bytes[]代替,直接不序列化该字段 20 | private byte[] bitSetBytes;//dubbo无法序列化BitSet,这里先转化为字节处理 21 | 22 | public StatObject() { 23 | 24 | } 25 | 26 | public StatObject(boolean isEmpty, long groupId, String extra) { 27 | this.isEmpty = isEmpty; 28 | this.groupId = groupId; 29 | this.extra = extra; 30 | } 31 | 32 | public StatObject(boolean isEmpty, long groupId, String extra, int base, BitSet bitSet, 33 | byte[] bitSetBytes) { 34 | this.isEmpty = isEmpty; 35 | this.groupId = groupId; 36 | this.base = base; 37 | this.bitSet = bitSet; 38 | this.extra = extra; 39 | this.bitSetBytes = bitSetBytes; 40 | } 41 | 42 | public boolean isEmpty() { 43 | return isEmpty; 44 | } 45 | 46 | public void setEmpty(boolean isEmpty) { 47 | this.isEmpty = isEmpty; 48 | } 49 | 50 | public int getBase() { 51 | return base; 52 | } 53 | 54 | public void setBase(int base) { 55 | this.base = base; 56 | } 57 | 58 | public BitSet getBitSet() { 59 | return bitSet; 60 | } 61 | 62 | public void setBitSet(BitSet bitSet) { 63 | this.bitSet = bitSet; 64 | } 65 | 66 | public String getExtra() { 67 | return extra; 68 | } 69 | 70 | public void setExtra(String extra) { 71 | this.extra = extra; 72 | } 73 | 74 | public long getGroupId() { 75 | return groupId; 76 | } 77 | 78 | public void setGroupId(long groupId) { 79 | this.groupId = groupId; 80 | } 81 | 82 | public byte[] getBitSetBytes() { 83 | return bitSetBytes; 84 | } 85 | 86 | public void setBitSetBytes(byte[] bitSetBytes) { 87 | this.bitSetBytes = bitSetBytes; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/utils/CollectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | public class CollectionUtils { 7 | public static boolean isEmpty(Collection collection) { 8 | return (collection == null || collection.isEmpty()); 9 | } 10 | 11 | public static boolean isEmpty(Map map) { 12 | return (map == null || map.isEmpty()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/utils/DataCodec.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import in.srain.binpack.BinPack; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * 编解码工具 9 | * 10 | * @author sumory.wu 11 | * @date 2015年1月18日 下午3:52:07 12 | */ 13 | public class DataCodec { 14 | 15 | public static byte[] encode(Map obj) { 16 | return BinPack.encode(obj, "UTF-8"); 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | public static Map decode(byte[] obj) { 21 | return (Map) BinPack.decode(obj, "UTF-8"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class DateUtil { 8 | 9 | private static final ThreadLocal DATE_FORMAT = new ThreadLocal() { 10 | @Override 11 | protected SimpleDateFormat initialValue() { 12 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 13 | //format.setTimeZone(TimeZone.getTimeZone("GMT")); 14 | return format; 15 | } 16 | }; 17 | 18 | public static String toDateTimeString(Date datetime) { 19 | return DATE_FORMAT.get().format(datetime); 20 | } 21 | 22 | public static Date parseDateTimeFromString(String datetime) { 23 | Date date = tryToParse(datetime, DATE_FORMAT.get()); 24 | return date; 25 | } 26 | 27 | public static long parseDateTimeToMilliseconds(String datetime) { 28 | Date date = parseDateTimeFromString(datetime); 29 | if (date != null) { 30 | return date.getTime(); 31 | } 32 | return 0; 33 | } 34 | 35 | private static Date tryToParse(String datetime, SimpleDateFormat format) { 36 | try { 37 | return format.parse(datetime); 38 | } 39 | catch (ParseException e) { 40 | return null; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/utils/IdUtil.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import com.sumory.gru.common.id.IdWorker; 4 | 5 | public class IdUtil { 6 | private static IdWorker messageIdWorker = new IdWorker(0); 7 | private static IdWorker clientIdWorker = new IdWorker(1); 8 | 9 | public static long generateMsgId() { 10 | try { 11 | return messageIdWorker.nextId(); 12 | } 13 | catch (Exception e) { 14 | e.printStackTrace(); 15 | return -1; 16 | } 17 | } 18 | 19 | public static long generateClientId() { 20 | try { 21 | return clientIdWorker.nextId(); 22 | } 23 | catch (Exception e) { 24 | e.printStackTrace(); 25 | return -1; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/utils/TokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import java.security.MessageDigest; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class TokenUtil { 10 | private final static Logger logger = LoggerFactory.getLogger(TokenUtil.class); 11 | 12 | public static String genToken(String source, String salt) { 13 | if (StringUtils.isBlank(source) || StringUtils.isBlank(salt)) 14 | return ""; 15 | 16 | String str = source + "_" + salt; 17 | byte[] buf = str.getBytes(); 18 | MessageDigest md5; 19 | try { 20 | md5 = MessageDigest.getInstance("MD5"); 21 | md5.update(buf); 22 | byte[] hash = md5.digest(); 23 | String d = ""; 24 | int usbyte = 0; // unsigned byte 25 | for (int i = 0; i < hash.length; i += 2) { // format with 2-byte words with spaces. 26 | usbyte = hash[i] & 0xFF; 27 | if (usbyte < 16) 28 | d += "0" + Integer.toHexString(usbyte); 29 | else 30 | d += Integer.toHexString(usbyte); 31 | usbyte = hash[i + 1] & 0xFF; 32 | if (usbyte < 16) 33 | d += "0" + Integer.toHexString(usbyte); 34 | else 35 | d += Integer.toHexString(usbyte); // + " "; 36 | } 37 | return d.trim().toLowerCase(); 38 | } 39 | catch (Exception e) { 40 | logger.error("生成token出错", e); 41 | return ""; 42 | } 43 | } 44 | 45 | public static void main(String[] args) { 46 | long t1 = System.currentTimeMillis(); 47 | for (int i = 0; i < 1; i++) { 48 | System.out.println(genToken("123456", "salt"));//397a79aca9daa9aa6b06dfd834e23c81 49 | System.out.println(genToken("123456我是谁", "salt"));//05449c97faaf7df7efe43cefc6c37631 50 | } 51 | long t2 = System.currentTimeMillis(); 52 | System.out.println(t2 - t1); 53 | 54 | System.out.println(genToken("652_mock_student_652", "token_gen_for_ticket@sumory.com")); 55 | System.out.println(genToken("652_mock_student_652_01500389b27446edc335fa6a68281cdb", 56 | "token_gen_for_spear@sumory.com")); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/zk/NotifyListener.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.zk; 2 | 3 | import org.apache.zookeeper.ZooKeeper; 4 | 5 | public interface NotifyListener { 6 | 7 | public void action(ZooKeeper zookeeper); 8 | 9 | } -------------------------------------------------------------------------------- /common/src/main/java/com/sumory/gru/common/zk/ZkNode.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.zk; 2 | 3 | import org.apache.zookeeper.CreateMode; 4 | 5 | public class ZkNode { 6 | private String path; 7 | private byte[] data; 8 | private CreateMode createMode; 9 | 10 | public ZkNode(String path, byte[] data, CreateMode createMode) { 11 | this.path = path; 12 | this.data = data; 13 | this.createMode = createMode; 14 | } 15 | 16 | public String getPath() { 17 | return path; 18 | } 19 | 20 | public void setPath(String path) { 21 | this.path = path; 22 | } 23 | 24 | public byte[] getData() { 25 | return data; 26 | } 27 | 28 | public void setData(byte[] data) { 29 | this.data = data; 30 | } 31 | 32 | public CreateMode getCreateMode() { 33 | return createMode; 34 | } 35 | 36 | public void setCreateMode(CreateMode createMode) { 37 | this.createMode = createMode; 38 | } 39 | 40 | public String toString() { 41 | return String.format("{path:%s, data:%s, createMode:%s}", path, new String(data), 42 | createMode.toString()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/src/test/java/com/sumory/gru/common/utils/DataCodecTest.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.junit.Test; 8 | 9 | import com.sumory.gru.common.utils.DataCodec; 10 | 11 | public class DataCodecTest { 12 | 13 | @Test 14 | public void testCodec() throws UnsupportedEncodingException { 15 | String strToBs = "All men are created equal."; 16 | Map m = new HashMap(); 17 | m.put("abc", 32); 18 | m.put("hello", "You jump, I jump"); 19 | m.put("world", strToBs.getBytes("UTF-8")); 20 | m.put("float", 12345.6789E123); 21 | 22 | byte[] tmp = DataCodec.encode(m); 23 | Map obj = (Map) DataCodec.decode(tmp); 24 | byte[] bs = (byte[]) obj.get("world"); 25 | System.out.println(m); 26 | System.out.println(obj); 27 | System.out.println(new String(bs, "UTF-8").equals(strToBs)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/src/test/java/com/sumory/gru/common/utils/DateUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.Date; 5 | 6 | import org.junit.Test; 7 | 8 | import com.sumory.gru.common.utils.DateUtil; 9 | 10 | public class DateUtilsTest { 11 | 12 | @Test 13 | public void testCodec() throws UnsupportedEncodingException { 14 | 15 | System.out.println(DateUtil.parseDateTimeFromString("2015-01-02 10:10:10")); 16 | System.out.println(DateUtil.parseDateTimeToMilliseconds("2015-01-02 10:10:10")); 17 | System.out.println(DateUtil.toDateTimeString(new Date())); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/test/java/com/sumory/gru/common/utils/IdUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import junit.framework.Assert; 4 | 5 | import org.junit.Test; 6 | 7 | import com.sumory.gru.common.utils.IdUtil; 8 | 9 | public class IdUtilsTest { 10 | 11 | @Test 12 | public void test() throws Exception { 13 | for (int i = 0; i < 100; i++) { 14 | long id = IdUtil.generateMsgId(); 15 | System.out.println(id); 16 | Assert.assertNotNull(id); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/dashboard-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/docs/dashboard-client.png -------------------------------------------------------------------------------- /docs/dashboard-monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/docs/dashboard-monitor.png -------------------------------------------------------------------------------- /docs/架构草图0.2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/docs/架构草图0.2.jpg -------------------------------------------------------------------------------- /docs/登陆策略.md: -------------------------------------------------------------------------------- 1 | 参考资料: 2 | 3 | 以下摘自 https://github.com/cloudwu/skynet/wiki/MsgServer 4 | 5 | 消息服务器 msgserver (M) 必须和 LoginServer (L) 一起使用。用户 C 的业务流程通常是这样的: 6 | 7 | C 向 L 表明想登陆 M。 8 | L 验证 C 的身份,交换 secret ,并转告 M 说 C 想登陆。 9 | M 确认 C 的 secret ,做好登陆准备,生成一个 subid (subid 用于多重登陆)。 10 | L 构造当次登陆用的用户名(用 uid 和 subid 联合生成)返回给 C 向 C 确认可以登陆。 11 | C 以这个用户名加上一个自增序列号(用于断线重连),利用 secret 签名和 M 握手接入。 12 | M 检查签名是否正确,以及序列号是否使用过(一次有效)。然后等待 M 的请求,并在当前连接上回应。 13 | 如果 C 和 M 断开连接,并不意味着用户从系统登出,这时 C 可以自增序列号,重新和 M 握手。这称为修复连接,C 不需要重复进行和 L 的登陆认证流程。 -------------------------------------------------------------------------------- /idgen/src/main/assembly/package-idgen-jar.xml: -------------------------------------------------------------------------------- 1 | 5 | dist 6 | 7 | jar 8 | 9 | false 10 | 11 | 12 | ${project.build.directory}/classes 13 | 14 | com/sumory/gru/idgen/service/*.class 15 | 16 | / 17 | 18 | 19 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /idgen/src/main/assembly/shared-jar-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | gru 6 | idgen 7 | gru-idgen-shared-jar 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /idgen/src/main/java/com/sumory/gru/idgen/id/IdUtils.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.idgen.id; 2 | 3 | import com.sumory.gru.common.config.Config; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * id生成工具 9 | * 10 | * @author sumory.wu 11 | * @date 2015年3月13日 下午7:15:32 12 | */ 13 | public class IdUtils { 14 | private static final Logger logger = LoggerFactory.getLogger(IdUtils.class); 15 | 16 | static IdWorker msgIdWorker = new IdWorker(Integer.parseInt(Config.get("id.generator.worker"))); 17 | 18 | public static long genMsgId() throws Exception { 19 | try { 20 | long genId = msgIdWorker.nextId(); 21 | logger.info("当前workerId为:" + msgIdWorker.getWorkerId() + " 生成id:" + genId); 22 | return genId; 23 | } 24 | catch (Exception e) { 25 | logger.error("生成msg id发生异常", e); 26 | throw e; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /idgen/src/main/java/com/sumory/gru/idgen/service/IdService.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.idgen.service; 2 | 3 | public interface IdService { 4 | public String getServiceVersion(); 5 | 6 | public long getMsgId(); 7 | } 8 | -------------------------------------------------------------------------------- /idgen/src/main/java/com/sumory/gru/idgen/service/impl/IdServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.idgen.service.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.sumory.gru.idgen.id.IdUtils; 9 | import com.sumory.gru.idgen.service.IdService; 10 | 11 | /** 12 | * id生成服务 13 | * 14 | * @author sumory.wu 15 | * @date 2015年3月15日 上午11:16:14 16 | */ 17 | @Service(value = "idService") 18 | public class IdServiceImpl implements IdService { 19 | private static final Logger logger = LoggerFactory.getLogger(IdServiceImpl.class); 20 | 21 | @Value("${dubbo.gru.idgen.version}") 22 | private String version; 23 | 24 | @Override 25 | public String getServiceVersion() { 26 | return version; 27 | } 28 | 29 | /** 30 | * 生成消息id 31 | */ 32 | @Override 33 | public long getMsgId() { 34 | try { 35 | return IdUtils.genMsgId(); 36 | } 37 | catch (Exception e) { 38 | logger.error("id生成出现严重异常", e); 39 | return Long.MIN_VALUE; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /idgen/src/main/resources/bin/dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source /etc/profile 3 | 4 | cd `dirname $0` 5 | BIN_DIR=`pwd` 6 | cd .. 7 | DEPLOY_DIR=`pwd` 8 | CONF_DIR=$DEPLOY_DIR/conf 9 | 10 | SERVER_NAME=`sed '/dubbo.application.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` 11 | LOGS_FILE=`sed '/dubbo.log4j.file/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` 12 | 13 | if [ -z "$SERVER_NAME" ]; then 14 | SERVER_NAME=`hostname` 15 | fi 16 | 17 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$CONF_DIR" |awk '{print $2}'` 18 | if [ -z "$PIDS" ]; then 19 | echo "ERROR: The $SERVER_NAME does not started!" 20 | exit 1 21 | fi 22 | 23 | LOGS_DIR="" 24 | if [ -n "$LOGS_FILE" ]; then 25 | LOGS_DIR=`dirname $LOGS_FILE` 26 | else 27 | LOGS_DIR=/data/logs/dubbo 28 | fi 29 | if [ ! -d $LOGS_DIR ]; then 30 | mkdir $LOGS_DIR 31 | fi 32 | DUMP_DIR=$LOGS_DIR/dump 33 | if [ ! -d $DUMP_DIR ]; then 34 | mkdir $DUMP_DIR 35 | fi 36 | DUMP_DATE=`date +%Y%m%d%H%M%S` 37 | DATE_DIR=$DUMP_DIR/$DUMP_DATE 38 | if [ ! -d $DATE_DIR ]; then 39 | mkdir $DATE_DIR 40 | fi 41 | 42 | echo -e "Dumping the $SERVER_NAME ...\c" 43 | for PID in $PIDS ; do 44 | jstack $PID > $DATE_DIR/jstack-$PID.dump 2>&1 45 | echo -e ".\c" 46 | jinfo $PID > $DATE_DIR/jinfo-$PID.dump 2>&1 47 | echo -e ".\c" 48 | jstat -gcutil $PID > $DATE_DIR/jstat-gcutil-$PID.dump 2>&1 49 | echo -e ".\c" 50 | jstat -gccapacity $PID > $DATE_DIR/jstat-gccapacity-$PID.dump 2>&1 51 | echo -e ".\c" 52 | jmap $PID > $DATE_DIR/jmap-$PID.dump 2>&1 53 | echo -e ".\c" 54 | jmap -heap $PID > $DATE_DIR/jmap-heap-$PID.dump 2>&1 55 | echo -e ".\c" 56 | jmap -histo $PID > $DATE_DIR/jmap-histo-$PID.dump 2>&1 57 | echo -e ".\c" 58 | if [ -r /usr/sbin/lsof ]; then 59 | /usr/sbin/lsof -p $PID > $DATE_DIR/lsof-$PID.dump 60 | echo -e ".\c" 61 | fi 62 | done 63 | if [ -r /bin/netstat ]; then 64 | /bin/netstat -an > $DATE_DIR/netstat.dump 2>&1 65 | echo -e ".\c" 66 | fi 67 | if [ -r /usr/bin/iostat ]; then 68 | /usr/bin/iostat > $DATE_DIR/iostat.dump 2>&1 69 | echo -e ".\c" 70 | fi 71 | if [ -r /usr/bin/mpstat ]; then 72 | /usr/bin/mpstat > $DATE_DIR/mpstat.dump 2>&1 73 | echo -e ".\c" 74 | fi 75 | if [ -r /usr/bin/vmstat ]; then 76 | /usr/bin/vmstat > $DATE_DIR/vmstat.dump 2>&1 77 | echo -e ".\c" 78 | fi 79 | if [ -r /usr/bin/free ]; then 80 | /usr/bin/free -t > $DATE_DIR/free.dump 2>&1 81 | echo -e ".\c" 82 | fi 83 | if [ -r /usr/bin/sar ]; then 84 | /usr/bin/sar > $DATE_DIR/sar.dump 2>&1 85 | echo -e ".\c" 86 | fi 87 | if [ -r /usr/bin/uptime ]; then 88 | /usr/bin/uptime > $DATE_DIR/uptime.dump 2>&1 89 | echo -e ".\c" 90 | fi 91 | echo "OK!" 92 | echo "DUMP: $DATE_DIR" 93 | -------------------------------------------------------------------------------- /idgen/src/main/resources/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd `dirname $0` 4 | BIN_DIR=`pwd` 5 | cd .. 6 | DEPLOY_DIR=`pwd` 7 | CONF_DIR=$DEPLOY_DIR/conf 8 | 9 | echo "curr dir is $DEPLOY_DIR" 10 | 11 | 12 | SERVER_NAME=gru_idgen 13 | 14 | STDOUT_FILE=$DEPLOY_DIR/stdout.log 15 | 16 | LIB_DIR=$DEPLOY_DIR/lib 17 | LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` 18 | 19 | JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " 20 | 21 | 22 | #RMI_SERVER_HOSTNAME=10.60.0.67 23 | #JMX_REMOTE_PORT=10900 24 | #JAVA_JMX_OPTS=" -Djava.rmi.server.hostname="$RMI_SERVER_HOSTNAME" -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port="$JMX_REMOTE_PORT" -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " 25 | JAVA_JMX_OPTS="" 26 | 27 | 28 | JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " 29 | 30 | 31 | echo -e "Starting the $SERVER_NAME ...\c" 32 | nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.sumory.gru.idgen.main.IdGenMain > $STDOUT_FILE 2>&1 & 33 | 34 | 35 | echo "OK!" 36 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$DEPLOY_DIR" | awk '{print $2}'` 37 | echo "PID: $PIDS" 38 | echo $PIDS>pid 39 | echo "STDOUT: $STDOUT_FILE" 40 | -------------------------------------------------------------------------------- /idgen/src/main/resources/bin/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | BIN_DIR=`pwd` 4 | cd .. 5 | DEPLOY_DIR=`pwd` 6 | CONF_DIR=$DEPLOY_DIR/conf 7 | 8 | SERVER_NAME=`sed '/dubbo.application.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` 9 | 10 | if [ -z "$SERVER_NAME" ]; then 11 | SERVER_NAME=`hostname` 12 | fi 13 | 14 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$CONF_DIR" |awk '{print $2}'` 15 | if [ -z "$PIDS" ]; then 16 | echo "ERROR: The $SERVER_NAME does not started!" 17 | exit 1 18 | fi 19 | 20 | if [ "$1" != "skip" ]; then 21 | $BIN_DIR/dump.sh 22 | fi 23 | 24 | echo -e "Stopping the $SERVER_NAME ...\c" 25 | for PID in $PIDS ; do 26 | kill $PID > /dev/null 2>&1 27 | done 28 | 29 | COUNT=0 30 | while [ $COUNT -lt 1 ]; do 31 | echo -e ".\c" 32 | sleep 1 33 | COUNT=1 34 | for PID in $PIDS ; do 35 | PID_EXIST=`ps --no-heading -p $PID` 36 | if [ -n "$PID_EXIST" ]; then 37 | COUNT=0 38 | break 39 | fi 40 | done 41 | done 42 | echo "OK!" 43 | echo "PID: $PIDS" 44 | -------------------------------------------------------------------------------- /idgen/src/main/resources/profiles/dev/dubbo.properties: -------------------------------------------------------------------------------- 1 | dubbo.container=spring 2 | dubbo.application.name=gru-provider-idgen-service 3 | dubbo.spring.config=classpath*:applicationContext*.xml 4 | dubbo.protocol.name=dubbo 5 | dubbo.service.loadbalance=roundrobin 6 | 7 | #dubbo.registry.client=curator 8 | dubbo.registry.address=zookeeper://192.168.100.183:2181 9 | dubbo.registry.file=/data/logs/idgen/registry.cache 10 | dubbo.protocol.port=33000 11 | 12 | ###### dubbo service api version ###### 13 | dubbo.gru.idgen.version=dev -------------------------------------------------------------------------------- /idgen/src/main/resources/profiles/dev/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/idgen 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /idgen/src/main/resources/profiles/dev/system.properties: -------------------------------------------------------------------------------- 1 | ############################## id generator worker config############################# 2 | id.generator.worker=0 -------------------------------------------------------------------------------- /idgen/src/main/resources/profiles/test1/dubbo.properties: -------------------------------------------------------------------------------- 1 | dubbo.container=spring 2 | dubbo.application.name=gru-provider-idgen-service 3 | dubbo.spring.config=classpath*:applicationContext*.xml 4 | dubbo.protocol.name=dubbo 5 | dubbo.service.loadbalance=roundrobin 6 | 7 | dubbo.registry.client=curator 8 | dubbo.registry.address=zookeeper://192.168.100.183:2181 9 | dubbo.registry.file=/data/logs/idgen/registry.cache 10 | dubbo.protocol.port=33000 11 | 12 | ###### dubbo service api version ###### 13 | dubbo.gru.idgen.version=1.0 14 | 15 | -------------------------------------------------------------------------------- /idgen/src/main/resources/profiles/test1/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/idgen 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /idgen/src/main/resources/profiles/test1/system.properties: -------------------------------------------------------------------------------- 1 | ############################## id generator worker config############################# 2 | id.generator.worker=1 -------------------------------------------------------------------------------- /idgen/src/main/resources/spring/applicationContext-provider-idgen.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /idgen/src/main/resources/spring/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /minions/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /minions/Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.test.js 2 | TIMEOUT = 200000 3 | REPORTER = spec 4 | NODE_ENV = dev 5 | 6 | test: 7 | @NODE_ENV=${NODE_ENV} ./node_modules/mocha/bin/mocha \ 8 | --timeout $(TIMEOUT) \ 9 | --reporter ${REPORTER} \ 10 | $(TESTS) 11 | 12 | 13 | echo: 14 | echo "hello.."\ 15 | "this is Makefile" 16 | 17 | .PHONY: test echo 18 | -------------------------------------------------------------------------------- /minions/README.md: -------------------------------------------------------------------------------- 1 | ### introduction 2 | 3 | Gru监控 -------------------------------------------------------------------------------- /minions/config/dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | port: 40000, 5 | viewEngine: 'ejs', 6 | 7 | views: path.resolve(__dirname, '..', 'views'), 8 | staticPath: path.resolve(__dirname, '..', 'public'), 9 | 10 | env: 'dev', 11 | logfile: path.resolve(__dirname, '..', 'logs/access.log'), 12 | 13 | sessionSecret: 'session_secret_random_seed', 14 | 15 | //redis config 16 | redis: {"address": "192.168.100.185", "port": "6379", "passwd": ""}, 17 | 18 | //不需要过滤是否登陆状态的白名单 19 | whitelist: [ 20 | "/", 21 | "/auth/login", 22 | "/version" 23 | ], 24 | 25 | zk: { 26 | addr: "192.168.100.183:2181", 27 | spears: "/spearnodes_dev" 28 | }, 29 | 30 | statServer: "http://192.168.100.122:50000" 31 | 32 | }; -------------------------------------------------------------------------------- /minions/config/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | // 通过NODE_ENV设置环境变量,默认为dev环境 4 | var env = process.env.NODE_ENV || 'dev'; 5 | env = env.toLowerCase(); 6 | 7 | var file = path.resolve(__dirname, env); 8 | try { 9 | var config = module.exports = require(file); 10 | console.log('Load config: [%s] %s', env, file); 11 | } catch (err) { 12 | console.error('Error when load config: [%s] %s', env, file); 13 | throw err; 14 | } -------------------------------------------------------------------------------- /minions/config/prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | port: 40000, 5 | viewEngine: 'ejs', 6 | 7 | views: path.resolve(__dirname, '..', 'views'), 8 | staticPath: path.resolve(__dirname, '..', 'public'), 9 | 10 | env: 'dev', 11 | logfile: path.resolve(__dirname, '..', 'logs/access.log'), 12 | 13 | sessionSecret: 'session_secret_random_seed', 14 | 15 | //redis config 16 | "redis": {"address": "172.18.4.174", "port": "6379", "passwd": ""}, 17 | 18 | //不需要过滤是否登陆状态的白名单 19 | "whitelist": [ 20 | "/", 21 | "/auth/login", 22 | "/version" 23 | ], 24 | 25 | zk: { 26 | addr: "172.18.4.227:2181,172.18.4.162:2181,172.18.4.166:2181", 27 | spears: "/spearnodes_prod" 28 | }, 29 | 30 | statServer: "http://172.18.4.172:50000" 31 | }; -------------------------------------------------------------------------------- /minions/config/test.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | port: 40000, 5 | viewEngine: 'ejs', 6 | 7 | views: path.resolve(__dirname, '..', 'views'), 8 | staticPath: path.resolve(__dirname, '..', 'public'), 9 | 10 | env: 'dev', 11 | logfile: path.resolve(__dirname, '..', 'logs/access.log'), 12 | 13 | sessionSecret: 'session_secret_random_seed', 14 | 15 | //redis config 16 | "redis": {"address": "192.168.100.185", "port": "6379", "passwd": ""}, 17 | 18 | //不需要过滤是否登陆状态的白名单 19 | "whitelist": [ 20 | "/", 21 | "/auth/login", 22 | "/version" 23 | ], 24 | 25 | zk: { 26 | addr: "192.168.100.183:2181", 27 | spears: "/spearnodes_test" 28 | }, 29 | 30 | statServer: "http://192.168.100.122:50000" 31 | 32 | }; -------------------------------------------------------------------------------- /minions/lib/log.js: -------------------------------------------------------------------------------- 1 | var log4js = require('log4js'); 2 | var config = require('../config'); 3 | 4 | log4js.configure({ 5 | appenders: [{ 6 | type: 'console' 7 | }, //控制台输出 8 | { 9 | type: 'file', //文件输出 10 | filename: config.logfile, 11 | //maxLogSize: 20480, //当超过maxLogSize大小时,会自动生成一个新文件 12 | //backups: 3 13 | pattern: "-yyyy-MM-dd", 14 | alwaysIncludePattern: true 15 | } 16 | ], 17 | replaceConsole: true //增加replaceConsole配置,让所有console输出到日志中,以[INFO] console代替console默认样式。 18 | }); 19 | 20 | 21 | exports.logger = function(name,level){ 22 | var logger = log4js.getLogger(name); 23 | logger.setLevel(level || 'INFO'); 24 | return logger; 25 | } -------------------------------------------------------------------------------- /minions/lib/redisUtils.js: -------------------------------------------------------------------------------- 1 | var redis = require("redis"); 2 | var config = require('../config'); 3 | 4 | var client = redis.createClient(config.redis.port, config.redis.address); 5 | //client.auth(config.redis.passwd, function(){console.log("redis authRouter ok, redisUtils initializes ok")}); 6 | 7 | /** 8 | * keys命令 9 | */ 10 | exports.keys = function (regex, callback) { 11 | if(regex){ 12 | client.keys(regex,function (err, reply) { 13 | if (err) 14 | console.log('redisUtils keys ' + regex + ' error: ' + err); 15 | //console.log('redisUtils keys: [' + regex + '] reply is:' + reply); 16 | callback && callback(err, reply); 17 | }); 18 | } 19 | }; 20 | 21 | 22 | /** 23 | * 设置一个不过期的值 24 | * 25 | * @param key 26 | * @param value 27 | * @param callback 28 | */ 29 | exports.set = function (key, value, callback) { 30 | if(key){ 31 | client.set(key, value,function (err, reply) { 32 | if (err) 33 | console.log('redisUtils set ' + key + ' error: ' + err); 34 | console.log('redisUtils set: [' + key + ']:[' + value + '] reply is:' + reply); 35 | callback && callback(err, reply); 36 | }); 37 | } 38 | }; 39 | 40 | /** 41 | * 设置带过期时间的值 42 | * 43 | * @param key 44 | * @param value 45 | * @param seconds 多少秒后过期 46 | * @param callback 47 | */ 48 | exports.setWithExpire = function(key, value, seconds, callback){ 49 | if(key && seconds){ 50 | client.setex(key, seconds, value,function (err, reply) { 51 | if (err) 52 | console.log('redisUtils setex ' + key + ' error: ' + err); 53 | console.log('redisUtils setex: [' + key + ']:[' + value + '] reply is:' + reply); 54 | callback && callback(err, reply); 55 | }); 56 | } 57 | }; 58 | 59 | /** 60 | * 获取值 61 | * 62 | * @param key 63 | * @param callback 64 | */ 65 | exports.get = function (key, callback) { 66 | client.get(key, function (err, reply) { 67 | if (err) 68 | console.log('redisUtils get ' + key + ' error:' + err); 69 | callback && callback(err, reply); 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /minions/lib/zkUtils.js: -------------------------------------------------------------------------------- 1 | var zookeeper = require('node-zookeeper-client'); 2 | var config = require("../config"); 3 | 4 | var client = zookeeper.createClient(config.zk.addr); 5 | 6 | client.once('connected', function () { 7 | console.log('Connected to the zookeeper server.'); 8 | }); 9 | 10 | client.connect(); 11 | 12 | 13 | exports.getSpears = function (callback) { 14 | var path = config.zk.spears; 15 | client.removeAllListeners(); 16 | client.getChildren(path, 17 | function (error, children, stat) { 18 | if (error) { 19 | console.log( 20 | 'Failed to list children of %s due to: %s.', 21 | path, 22 | error 23 | ); 24 | callback(error); 25 | } 26 | console.log('Children of %s are: %j.', path, children); 27 | callback(null, children); 28 | } 29 | ); 30 | } -------------------------------------------------------------------------------- /minions/models/adminModel.js: -------------------------------------------------------------------------------- 1 | var logger = require("../lib/log.js").logger("adminModel"); 2 | var commonUtils = require("../lib/utils.js"); 3 | 4 | -------------------------------------------------------------------------------- /minions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"minions", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=0.10.x" 7 | }, 8 | "author": { 9 | 10 | }, 11 | "scripts": { 12 | "start": "node ./app.js", 13 | "test": "make test" 14 | }, 15 | "dependencies": { 16 | "express": "~4.0.0", 17 | "static-favicon": "~1.0.0", 18 | "cookie": "0.1.0", 19 | "cookie-parser": "~1.0.1", 20 | "body-parser": "~1.0.0", 21 | "debug": "~0.7.4", 22 | "ejs": "~0.8.5", 23 | "method-override": "1.0.0", 24 | "express-session": "1.0.2", 25 | "multiparty": "3.2.4", 26 | "redis": "0.10.1", 27 | "node-zookeeper-client":"*", 28 | "mysql": "2.4.2", 29 | "log4js": "0.6.16", 30 | "request":"2.53.0" 31 | }, 32 | "devDependencies": { 33 | "should": "*", 34 | "mocha": "*" 35 | } 36 | } -------------------------------------------------------------------------------- /minions/public/css/minions.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0 30px 20px 30px; 3 | } 4 | 5 | #header-navigate{ 6 | margin-left:30px; 7 | } 8 | 9 | #header-navigate a{ 10 | margin-top:-12px; 11 | } 12 | 13 | .ui.avatar.image.monitor_logo{ 14 | margin-right:20px; 15 | margin-top:-8px; 16 | } 17 | 18 | #console { 19 | min-height: 300px; 20 | max-height: 300px; 21 | overflow: auto; 22 | } 23 | 24 | .username-msg { 25 | color: orange; 26 | } 27 | 28 | .connect-msg { 29 | color: green; 30 | } 31 | 32 | .disconnect-msg { 33 | color: red; 34 | } 35 | 36 | .send-msg { 37 | color: #888 38 | } 39 | 40 | .tip_text{ 41 | color:#999; 42 | } 43 | 44 | 45 | 46 | #navigate { 47 | margin-bottom: 40px; 48 | } 49 | 50 | #btnKickGroup { 51 | width: 220px; 52 | margin-top: 4px; 53 | margin-left: 10px; 54 | 55 | } 56 | 57 | #btnKickUser { 58 | width: 220px; 59 | margin-top: 4px; 60 | margin-left: 20px; 61 | padding-left: 25px; 62 | border-left: 1px #DDD solid; 63 | } 64 | 65 | #listenGroup { 66 | width: 280px; 67 | margin-top: 4px; 68 | margin-left: 20px; 69 | padding-left: 25px; 70 | border-left: 1px #DDD solid; 71 | } 72 | 73 | .ui.list .item a { 74 | color: #fff; 75 | } 76 | 77 | #groups_area { 78 | min-height: 30px; 79 | max-height: 513px; 80 | overflow-y: scroll; 81 | -ms-overflow-y: scroll; 82 | } 83 | 84 | #groups_area a { 85 | margin-top: 5px; 86 | width: 100px; 87 | } 88 | 89 | #group_area a { 90 | margin: 2px; 91 | min-width: 80px; 92 | text-align: center; 93 | } 94 | 95 | #spears_area a { 96 | padding: 0.8em !important; 97 | margin-bottom: 5px; 98 | } 99 | 100 | #spear_stat { 101 | margin-top: -20px; 102 | } 103 | 104 | #spear_stat .statistic { 105 | margin-right: 30px; 106 | } 107 | 108 | #spear_stat .statistic .value .icon { 109 | width: 40px; 110 | display: inline-block; 111 | } 112 | 113 | #spear_stat .statistic .value span { 114 | width: 30px; 115 | } 116 | 117 | #spear_stat i.small.icon { 118 | font-size: 0.5em 119 | } 120 | 121 | #spear_stat span { 122 | font-size: 0.4em 123 | } 124 | 125 | .custom_section{ 126 | margin-bottom:50px; 127 | } -------------------------------------------------------------------------------- /minions/public/img/gru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/img/gru.png -------------------------------------------------------------------------------- /minions/public/semantic/components/ad.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Ad 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2013 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.ad{display:block;overflow:hidden;margin:1em 0}.ui.ad:first-child,.ui.ad:last-child{margin:0}.ui.ad iframe{margin:0;padding:0;border:none;overflow:hidden}.ui.leaderboard.ad{width:728px;height:90px}.ui[class*="medium rectangle"].ad{width:300px;height:250px}.ui[class*="large rectangle"].ad{width:336px;height:280px}.ui[class*="half page"].ad{width:300px;height:600px}.ui.square.ad{width:250px;height:250px}.ui[class*="small square"].ad{width:200px;height:200px}.ui[class*="small rectangle"].ad{width:180px;height:150px}.ui[class*="vertical rectangle"].ad{width:240px;height:400px}.ui.button.ad{width:120px;height:90px}.ui[class*="square button"].ad{width:125px;height:125px}.ui[class*="small button"].ad{width:120px;height:60px}.ui.skyscraper.ad{width:120px;height:600px}.ui[class*="wide skyscraper"].ad{width:160px}.ui.banner.ad{width:468px;height:60px}.ui[class*="vertical banner"].ad{width:120px;height:240px}.ui[class*="top banner"].ad{width:930px;height:180px}.ui[class*="half banner"].ad{width:234px;height:60px}.ui[class*="large leaderboard"].ad{width:970px;height:90px}.ui.billboard.ad{width:970px;height:250px}.ui.panorama.ad{width:980px;height:120px}.ui.netboard.ad{width:580px;height:400px}.ui[class*="large mobile banner"].ad{width:320px;height:100px}.ui[class*="mobile leaderboard"].ad{width:320px;height:50px}.ui.mobile.ad{display:none}@media only screen and (max-width:767px){.ui.mobile.ad{display:block}}.ui.centered.ad{margin-left:auto;margin-right:auto}.ui.test.ad{position:relative;background:#333}.ui.test.ad:after{position:absolute;top:50%;left:50%;width:100%;text-align:center;-webkit-transform:translateX(-50%)translateY(-50%);-ms-transform:translateX(-50%)translateY(-50%);transform:translateX(-50%)translateY(-50%);content:'Ad';color:#fff;font-size:1em;font-weight:700}.ui.mobile.test.ad:after{font-size:.85714em}.ui.test.ad[data-text]:after{content:attr(data-text)} -------------------------------------------------------------------------------- /minions/public/semantic/components/breadcrumb.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Breadcrumb 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | 12 | 13 | /******************************* 14 | Breadcrumb 15 | *******************************/ 16 | 17 | .ui.breadcrumb { 18 | margin: 1em 0em; 19 | display: inline-block; 20 | vertical-align: middle; 21 | } 22 | .ui.breadcrumb:first-child { 23 | margin-top: 0em; 24 | } 25 | .ui.breadcrumb:last-child { 26 | margin-bottom: 0em; 27 | } 28 | 29 | 30 | /******************************* 31 | Content 32 | *******************************/ 33 | 34 | 35 | /* Divider */ 36 | .ui.breadcrumb .divider { 37 | display: inline-block; 38 | opacity: 0.5; 39 | margin: 0em 0.2rem 0em; 40 | font-size: 0.9em; 41 | color: rgba(0, 0, 0, 0.4); 42 | vertical-align: baseline; 43 | } 44 | 45 | /* Link */ 46 | .ui.breadcrumb a { 47 | color: #009fda; 48 | } 49 | .ui.breadcrumb a:hover { 50 | color: #00b2f3; 51 | } 52 | 53 | /* Icon Divider */ 54 | .ui.breadcrumb .icon.divider { 55 | font-size: 0.85714286em; 56 | vertical-align: baseline; 57 | } 58 | 59 | /* Section */ 60 | .ui.breadcrumb a.section { 61 | cursor: pointer; 62 | } 63 | .ui.breadcrumb .section { 64 | display: inline-block; 65 | margin: 0em; 66 | padding: 0em; 67 | } 68 | 69 | /* Loose Coupling */ 70 | .ui.breadcrumb.segment { 71 | display: inline-block; 72 | padding: 0.5em 1em; 73 | } 74 | 75 | 76 | /******************************* 77 | States 78 | *******************************/ 79 | 80 | .ui.breadcrumb .active.section { 81 | font-weight: bold; 82 | } 83 | 84 | 85 | /******************************* 86 | Variations 87 | *******************************/ 88 | 89 | .ui.mini.breadcrumb { 90 | font-size: 0.65em; 91 | } 92 | .ui.tiny.breadcrumb { 93 | font-size: 0.7em; 94 | } 95 | .ui.small.breadcrumb { 96 | font-size: 0.75em; 97 | } 98 | .ui.breadcrumb { 99 | font-size: 1em; 100 | } 101 | .ui.large.breadcrumb { 102 | font-size: 1.1em; 103 | } 104 | .ui.big.breadcrumb { 105 | font-size: 1.05em; 106 | } 107 | .ui.huge.breadcrumb { 108 | font-size: 1.3em; 109 | } 110 | .ui.massive.breadcrumb { 111 | font-size: 1.5em; 112 | } 113 | 114 | 115 | /******************************* 116 | Theme Overrides 117 | *******************************/ 118 | 119 | 120 | 121 | /******************************* 122 | Site Overrides 123 | *******************************/ 124 | 125 | -------------------------------------------------------------------------------- /minions/public/semantic/components/breadcrumb.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Breadcrumb 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.breadcrumb{margin:1em 0;display:inline-block;vertical-align:middle}.ui.breadcrumb:first-child{margin-top:0}.ui.breadcrumb:last-child{margin-bottom:0}.ui.breadcrumb .divider{display:inline-block;opacity:.5;margin:0 .2rem;font-size:.9em;color:rgba(0,0,0,.4);vertical-align:baseline}.ui.breadcrumb a{color:#009fda}.ui.breadcrumb a:hover{color:#00b2f3}.ui.breadcrumb .icon.divider{font-size:.85714286em;vertical-align:baseline}.ui.breadcrumb a.section{cursor:pointer}.ui.breadcrumb .section{display:inline-block;margin:0;padding:0}.ui.breadcrumb.segment{display:inline-block;padding:.5em 1em}.ui.breadcrumb .active.section{font-weight:700}.ui.mini.breadcrumb{font-size:.65em}.ui.tiny.breadcrumb{font-size:.7em}.ui.small.breadcrumb{font-size:.75em}.ui.breadcrumb{font-size:1em}.ui.large.breadcrumb{font-size:1.1em}.ui.big.breadcrumb{font-size:1.05em}.ui.huge.breadcrumb{font-size:1.3em}.ui.massive.breadcrumb{font-size:1.5em} -------------------------------------------------------------------------------- /minions/public/semantic/components/comment.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Comment 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributorss 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.comments .comment,.ui.comments .comment .comments .comment{border:none;border-top:none;background:0 0}.ui.comments{margin:1.5em 0;max-width:650px}.ui.comments:first-child{margin-top:0}.ui.comments:last-child{margin-bottom:0}.ui.comments .comment{position:relative;margin:.5em 0 0;padding:.5em 0 0;line-height:1.2}.ui.comments .comment:first-child{margin-top:0;padding-top:0}.ui.comments .comment .comments{margin:0 0 .5em .5em;padding:1em 0 1em 1em}.ui.comments .comment .comments:before{position:absolute;top:0;left:0}.ui.comments .comment .avatar{display:block;width:2.5em;height:auto;float:left;margin:.2em 0 0}.ui.comments .comment .avatar img,.ui.comments .comment img.avatar{display:block;margin:0 auto;width:100%;height:100%;border-radius:.25rem}.ui.comments .comment>.content{display:block}.ui.comments .comment>.avatar~.content{margin-left:3.5em}.ui.comments .comment .author{font-size:1em;color:rgba(0,0,0,.8);font-weight:700}.ui.comments .comment a.author{cursor:pointer}.ui.comments .comment a.author:hover{color:#00b2f3}.ui.comments .comment .metadata{display:inline-block;margin-left:.5em;color:rgba(0,0,0,.4);font-size:.875em}.ui.comments .comment .metadata>*{display:inline-block;margin:0 .5em 0 0}.ui.comments .comment .metadata>:last-child{margin-right:0}.ui.comments .comment .text{margin:.25em 0 .5em;font-size:1em;word-wrap:break-word;color:rgba(0,0,0,.8);line-height:1.3}.ui.comments .comment .actions{font-size:.875em}.ui.comments .comment .actions a{cursor:pointer;display:inline-block;margin:0 .75em 0 0;color:rgba(0,0,0,.4)}.ui.comments .comment .actions a:last-child{margin-right:0}.ui.comments .comment .actions a.active,.ui.comments .comment .actions a:hover{color:rgba(0,0,0,.8)}.ui.comments>.reply.form{margin-top:1em}.ui.comments .comment .reply.form{width:100%;margin-top:1em}.ui.comments .reply.form textarea{font-size:1em;height:12em}.ui.collapsed.comments,.ui.comments .collapsed.comment,.ui.comments .collapsed.comments{display:none}.ui.threaded.comments .comment .comments{margin:-1.5em 0 -1em 1.25em;padding:3em 0 2em 2.25em;box-shadow:-1px 0 0 rgba(39,41,43,.15)}.ui.minimal.comments .comment .actions{opacity:0;position:absolute;top:0;right:0;left:auto;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;-webkit-transition-delay:.1s;transition-delay:.1s}.ui.minimal.comments .comment>.content:hover>.actions{opacity:1}.ui.small.comments{font-size:.9em}.ui.comments{font-size:1em}.ui.large.comments{font-size:1.1em}.ui.huge.comments{font-size:1.2em} -------------------------------------------------------------------------------- /minions/public/semantic/components/dimmer.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Dimmer 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.dimmable{position:relative}.ui.dimmer{display:none;position:absolute;top:0!important;left:0!important;width:100%;height:100%;text-align:center;vertical-align:middle;background:rgba(0,0,0,.85);opacity:0;line-height:1;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-transition:background-color .5s linear;transition:background-color .5s linear;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;will-change:opacity;z-index:1000}.dimmed.dimmable>.ui.animating.dimmer,.dimmed.dimmable>.ui.visible.dimmer,.ui.active.dimmer,.ui.simple.dimmer{display:block;opacity:1}.ui.dimmer>.content{width:100%;height:100%;display:table;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ui.dimmer>.content>div{display:table-cell;vertical-align:middle;color:#fff}.ui.segment>.ui.dimmer{border-radius:inherit!important}.animating.dimmable:not(body),.dimmed.dimmable:not(body){overflow:hidden}.ui.disabled.dimmer{width:0!important;height:0!important}.ui.page.dimmer{position:fixed;-webkit-transform-style:'';transform-style:'';-webkit-perspective:2000px;perspective:2000px;-webkit-transform-origin:center center;-ms-transform-origin:center center;transform-origin:center center}body.animating.in.dimmable,body.dimmed.dimmable{overflow:hidden}body.dimmable>.dimmer{position:fixed}.ui.dimmer>.top.aligned.content>*{vertical-align:top}.ui.dimmer>.bottom.aligned.content>*{vertical-align:bottom}.ui.inverted.dimmer{background:rgba(255,255,255,.85)}.ui.inverted.dimmer>.content>*{color:#fff}.ui.simple.dimmer{overflow:hidden;width:0;height:0;z-index:-100;background-color:transparent}.dimmed.dimmable>.ui.simple.dimmer{overflow:visible;opacity:1;width:100%;height:100%;background:rgba(0,0,0,.85);z-index:1}.ui.simple.inverted.dimmer{background:rgba(255,255,255,0)}.dimmed.dimmable>.ui.simple.inverted.dimmer{background:rgba(255,255,255,.85)} -------------------------------------------------------------------------------- /minions/public/semantic/components/nag.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Nag 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.nag{display:none;opacity:.95;position:relative;top:0;left:0;z-index:999;min-height:0;width:100%;margin:0;padding:.75em 1em;background:#555;box-shadow:0 1px 2px 0 rgba(0,0,0,.2);font-size:1rem;text-align:center;color:rgba(0,0,0,.8);border-radius:0 0 .2857rem .2857rem;-webkit-transition:.2s background ease;transition:.2s background ease}a.ui.nag{cursor:pointer}.ui.nag>.title{display:inline-block;margin:0 .5em;color:#fff}.ui.nag>.close.icon{cursor:pointer;opacity:.4;position:absolute;top:50%;right:1em;font-size:1em;margin:-.5em 0 0;color:#fff;-webkit-transition:opacity .2s ease;transition:opacity .2s ease}.ui.nag:hover{background:#555;opacity:1}.ui.nag .close:hover{opacity:1}.ui.overlay.nag{position:absolute;display:block}.ui.fixed.nag{position:fixed}.ui.bottom.nag,.ui.bottom.nags{border-radius:.2857rem .2857rem 0 0;top:auto;bottom:0}.ui.inverted.nag,.ui.inverted.nags .nag{background-color:#f0f0f0;color:rgba(0,0,0,.85)}.ui.inverted.nag .close,.ui.inverted.nag .title,.ui.inverted.nags .nag .close,.ui.inverted.nags .nag .title{color:rgba(0,0,0,.4)}.ui.nags .nag{border-radius:0!important}.ui.nags .nag:last-child{border-radius:0 0 .2857rem .2857rem}.ui.bottom.nags .nag:last-child{border-radius:.2857rem .2857rem 0 0} -------------------------------------------------------------------------------- /minions/public/semantic/components/popup.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Popup 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.popup{display:none;position:absolute;top:0;right:0;min-width:-moz-max-content;z-index:1900;border:1px solid #ccc;max-width:250px;background-color:#fff;padding:.833em 1em;font-weight:400;font-style:normal;color:rgba(0,0,0,.8);border-radius:.2857rem;box-shadow:0 2px 4px rgba(0,0,0,.1);margin:0}.ui.bottom.center.popup:before,.ui.bottom.left.popup:before,.ui.bottom.right.popup:before{box-shadow:-1px -1px 0 0 #b3b3b3;top:-.325em}.ui.popup>.header{padding:0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1.125em;line-height:1.2;font-weight:700}.ui.popup>.header+.content{padding-top:.5em}.ui.popup:before{position:absolute;content:'';width:.75em;height:.75em;background:#fff;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);z-index:2;box-shadow:1px 1px 0 0 #b3b3b3}.ui.popup.bottom{margin:.75em 0 0}.ui.popup.top{margin:0 0 .75em}.ui.popup.left.center{margin:0 .75em 0 0}.ui.popup.right.center{margin:0 0 0 .75em}.ui.bottom.right.popup,.ui.top.right.popup{margin-right:0}.ui.bottom.center.popup:before{margin-left:-.325em;left:50%;right:auto;bottom:auto}.ui.bottom.left.popup{margin-left:0}.ui.bottom.left.popup:before{left:1em;right:auto;bottom:auto;margin-left:0}.ui.bottom.right.popup:before{right:1em;bottom:auto;left:auto;margin-left:0}.ui.top.center.popup:before{top:auto;right:auto;bottom:-.325em;left:50%;margin-left:-.325em}.ui.top.left.popup{margin-left:0}.ui.top.left.popup:before{bottom:-.325em;left:1em;top:auto;right:auto;margin-left:0}.ui.top.right.popup:before{bottom:-.325em;right:1em;top:auto;left:auto;margin-left:0}.ui.left.center.popup:before{top:50%;right:-.325em;bottom:auto;left:auto;margin-top:-.325em;box-shadow:1px -1px 0 0 #b3b3b3}.ui.right.center.popup:before{top:50%;left:-.325em;bottom:auto;right:auto;margin-top:-.325em;box-shadow:-1px 1px 0 0 #b3b3b3}.ui.popup>.ui.grid:not(.padded){width:-webkit-calc(100% + 1.75rem);width:calc(100% + 1.75rem);margin:-.7rem -.875rem}.ui.loading.popup{display:block;visibility:hidden;z-index:-1}.ui.animating.popup,.ui.visible.popup{display:block}.ui.basic.popup:before{display:none}.ui.wide.popup{max-width:350px}.ui[class*="very wide"].popup{max-width:550px}.ui.fluid.popup{width:100%;max-width:none}.ui.inverted.popup{background:#1b1c1d;color:#fff;border:none;box-shadow:none}.ui.inverted.popup .header{background-color:none;color:#fff}.ui.inverted.popup:before{background-color:#1b1c1d;box-shadow:none!important}.ui.flowing.popup{max-width:none}.ui.small.popup{font-size:.785714rem}.ui.popup{font-size:.85714rem}.ui.large.popup{font-size:1rem}.ui.huge.popup{font-size:1.14285rem} -------------------------------------------------------------------------------- /minions/public/semantic/components/rail.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Rail 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | 12 | 13 | /******************************* 14 | Rails 15 | *******************************/ 16 | 17 | .ui.rail { 18 | position: absolute; 19 | top: 0%; 20 | width: 300px; 21 | height: 100%; 22 | box-sizing: content-box; 23 | } 24 | .ui.left.rail { 25 | left: auto; 26 | right: 100%; 27 | padding: 0em 2rem 0em 0em; 28 | margin: 0em 2rem 0em 0em; 29 | } 30 | .ui.right.rail { 31 | left: 100%; 32 | right: auto; 33 | padding: 0em 0em 0em 2rem; 34 | margin: 0em 0em 0em 2rem; 35 | } 36 | 37 | 38 | /******************************* 39 | Variations 40 | *******************************/ 41 | 42 | 43 | /*-------------- 44 | Internal 45 | ---------------*/ 46 | 47 | .ui.left.internal.rail { 48 | left: 0%; 49 | right: auto; 50 | padding: 0em 0em 0em 2rem; 51 | margin: 0em 0em 0em 2rem; 52 | } 53 | .ui.right.internal.rail { 54 | left: auto; 55 | right: 0%; 56 | padding: 0em 2rem 0em 0em; 57 | margin: 0em 2rem 0em 0em; 58 | } 59 | 60 | /*-------------- 61 | Divided 62 | ---------------*/ 63 | 64 | .ui.left.dividing.rail { 65 | padding: 0em 2.5rem 0em 0em; 66 | margin: 0em 2.5rem 0em 0em; 67 | border-right: 1px solid rgba(39, 41, 43, 0.15); 68 | } 69 | .ui.right.dividing.rail { 70 | border-left: 1px solid rgba(39, 41, 43, 0.15); 71 | padding: 0em 0em 0em 2.5rem; 72 | margin: 0em 0em 0em 2.5rem; 73 | } 74 | 75 | /*-------------- 76 | Distance 77 | ---------------*/ 78 | 79 | .ui.close.left.rail { 80 | padding: 0em 1em 0em 0em; 81 | margin: 0em 1em 0em 0em; 82 | } 83 | .ui.close.right.rail { 84 | padding: 0em 0em 0em 1em; 85 | margin: 0em 0em 0em 1em; 86 | } 87 | .ui.very.close.left.rail { 88 | padding: 0em 0.5em 0em 0em; 89 | margin: 0em 0.5em 0em 0em; 90 | } 91 | .ui.very.close.right.rail { 92 | padding: 0em 0em 0em 0.5em; 93 | margin: 0em 0em 0em 0.5em; 94 | } 95 | 96 | /*-------------- 97 | Attached 98 | ---------------*/ 99 | 100 | .ui.attached.left.rail, 101 | .ui.attached.right.rail { 102 | padding: 0em; 103 | margin: 0em; 104 | } 105 | 106 | /*-------------- 107 | Sizing 108 | ---------------*/ 109 | 110 | .ui.rail { 111 | font-size: 1em; 112 | } 113 | 114 | 115 | /******************************* 116 | Theme Overrides 117 | *******************************/ 118 | 119 | 120 | 121 | /******************************* 122 | Site Overrides 123 | *******************************/ 124 | 125 | -------------------------------------------------------------------------------- /minions/public/semantic/components/rail.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Rail 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.rail{position:absolute;top:0;width:300px;height:100%;box-sizing:content-box;font-size:1em}.ui.left.rail{left:auto;right:100%;padding:0 2rem 0 0;margin:0 2rem 0 0}.ui.left.internal.rail,.ui.right.rail{right:auto;padding:0 0 0 2rem;margin:0 0 0 2rem}.ui.right.rail{left:100%}.ui.left.internal.rail{left:0}.ui.right.internal.rail{left:auto;right:0;padding:0 2rem 0 0;margin:0 2rem 0 0}.ui.left.dividing.rail{padding:0 2.5rem 0 0;margin:0 2.5rem 0 0;border-right:1px solid rgba(39,41,43,.15)}.ui.right.dividing.rail{border-left:1px solid rgba(39,41,43,.15);padding:0 0 0 2.5rem;margin:0 0 0 2.5rem}.ui.close.left.rail{padding:0 1em 0 0;margin:0 1em 0 0}.ui.close.right.rail{padding:0 0 0 1em;margin:0 0 0 1em}.ui.very.close.left.rail{padding:0 .5em 0 0;margin:0 .5em 0 0}.ui.very.close.right.rail{padding:0 0 0 .5em;margin:0 0 0 .5em}.ui.attached.left.rail,.ui.attached.right.rail{padding:0;margin:0} -------------------------------------------------------------------------------- /minions/public/semantic/components/reset.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Reset 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */img,legend{border:0}legend,td,th{padding:0}*,:after,:before{box-sizing:inherit}html{box-sizing:border-box;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}input[type=text],input[type=email],input[type=search],input[type=password]{-webkit-appearance:none;-moz-appearance:none}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}table{border-collapse:collapse;border-spacing:0} -------------------------------------------------------------------------------- /minions/public/semantic/components/shape.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Shape 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.shape.animating,.ui.shape.animating .sides{-webkit-transition:all .6s ease-in-out;transition:all .6s ease-in-out}.ui.shape{position:relative;display:inline-block;-webkit-perspective:2000px;perspective:2000px}.ui.shape .sides{-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.ui.shape .side{opacity:1;width:100%;margin:0!important;-webkit-backface-visibility:hidden;backface-visibility:hidden;display:none}.ui.shape .side>*{-webkit-backface-visibility:visible!important;backface-visibility:visible!important}.ui.cube.shape .side{min-width:15em;height:15em;padding:2em;background-color:#e6e6e6;color:rgba(0,0,0,.8);box-shadow:0 0 2px rgba(0,0,0,.3)}.ui.cube.shape .side>.content{width:100%;height:100%;display:table;text-align:center;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ui.cube.shape .side>.content>div{display:table-cell;vertical-align:middle;font-size:2em}.ui.text.shape.animating .sides{position:static}.ui.text.shape .side{white-space:nowrap}.ui.text.shape .side>*{white-space:normal}.ui.loading.shape{position:absolute;top:-9999px;left:-9999px}.ui.shape .animating.side{position:absolute;top:0;left:0;z-index:100}.ui.shape .hidden.side{opacity:.4}.ui.shape.animating .sides{position:absolute}.ui.shape.animating .side{-webkit-transition:opacity .6s ease-in-out;transition:opacity .6s ease-in-out}.ui.shape .active.side{display:block} -------------------------------------------------------------------------------- /minions/public/semantic/components/site.min.css: -------------------------------------------------------------------------------- 1 | body,html{font-size:14px}body,h1,h2,h3,h4,h5{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif}body,p{line-height:1.33}::selection,body{color:rgba(0,0,0,.8)}@import 'https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin';/*! 2 | * # Semantic UI - Site 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */body,html{height:100%}body{margin:0;padding:0;min-width:278px;background:#f7f7f7;font-smoothing:antialiased}h1,h2,h3,h4,h5{line-height:1.33em;margin:-webkit-calc(2rem - .165em)0 1rem;margin:calc(2rem - .165em)0 1rem;font-weight:700;padding:0}h1{min-height:1rem;font-size:2rem}h2{font-size:1.714rem}h3{font-size:1.28rem}h4{font-size:1.071rem}h5{font-size:1rem}p{margin:0 0 1em}p:first-child{margin-top:0}p:last-child{margin-bottom:0}a{color:#009fda;text-decoration:none}a:hover{color:#00b2f3}::-webkit-selection{background-color:#cce2ff;color:rgba(0,0,0,.8)}::-moz-selection{background-color:#cce2ff;color:rgba(0,0,0,.8)}::selection{background-color:#cce2ff}input::-webkit-selection,textarea::-webkit-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.8)}input::-moz-selection,textarea::-moz-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.8)}input::selection,textarea::selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.8)} -------------------------------------------------------------------------------- /minions/public/semantic/components/sticky.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Sticky 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | 12 | 13 | /******************************* 14 | Sticky 15 | *******************************/ 16 | 17 | .ui.sticky { 18 | position: static; 19 | -webkit-transition: width 0.2s ease, height 0.2s ease, top 0.2s ease, bottom 0.2s ease; 20 | transition: width 0.2s ease, height 0.2s ease, top 0.2s ease, bottom 0.2s ease; 21 | z-index: 800; 22 | } 23 | 24 | 25 | /******************************* 26 | States 27 | *******************************/ 28 | 29 | 30 | /* Bound */ 31 | .ui.sticky.bound { 32 | position: absolute; 33 | left: auto; 34 | right: auto; 35 | } 36 | 37 | /* Fixed */ 38 | .ui.sticky.fixed { 39 | position: fixed; 40 | left: auto; 41 | right: auto; 42 | } 43 | 44 | /* Bound/Fixed Position */ 45 | .ui.sticky.bound.top, 46 | .ui.sticky.fixed.top { 47 | top: 0px; 48 | bottom: auto; 49 | } 50 | .ui.sticky.bound.bottom, 51 | .ui.sticky.fixed.bottom { 52 | top: auto; 53 | bottom: 0px; 54 | } 55 | 56 | 57 | /******************************* 58 | Types 59 | *******************************/ 60 | 61 | .ui.native.sticky { 62 | position: -webkit-sticky; 63 | position: -moz-sticky; 64 | position: -ms-sticky; 65 | position: -o-sticky; 66 | position: sticky; 67 | } 68 | 69 | 70 | /******************************* 71 | Theme Overrides 72 | *******************************/ 73 | 74 | 75 | 76 | /******************************* 77 | Site Overrides 78 | *******************************/ 79 | 80 | -------------------------------------------------------------------------------- /minions/public/semantic/components/sticky.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Sticky 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.sticky{position:static;-webkit-transition:width .2s ease,height .2s ease,top .2s ease,bottom .2s ease;transition:width .2s ease,height .2s ease,top .2s ease,bottom .2s ease;z-index:800}.ui.sticky.bound{position:absolute;left:auto;right:auto}.ui.sticky.fixed{position:fixed;left:auto;right:auto}.ui.sticky.bound.top,.ui.sticky.fixed.top{top:0;bottom:auto}.ui.sticky.bound.bottom,.ui.sticky.fixed.bottom{top:auto;bottom:0}.ui.native.sticky{position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky} -------------------------------------------------------------------------------- /minions/public/semantic/components/tab.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Tab 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | 12 | 13 | /******************************* 14 | UI Tabs 15 | *******************************/ 16 | 17 | .ui.tab { 18 | display: none; 19 | } 20 | 21 | 22 | /******************************* 23 | States 24 | *******************************/ 25 | 26 | 27 | /*-------------------- 28 | Active 29 | ---------------------*/ 30 | 31 | .ui.tab.active, 32 | .ui.tab.open { 33 | display: block; 34 | } 35 | 36 | /*-------------------- 37 | Loading 38 | ---------------------*/ 39 | 40 | .ui.tab.loading { 41 | position: relative; 42 | overflow: hidden; 43 | display: block; 44 | min-height: 250px; 45 | } 46 | .ui.tab.loading * { 47 | position: relative !important; 48 | left: -10000px !important; 49 | } 50 | .ui.tab.loading:before, 51 | .ui.tab.loading.segment:before { 52 | position: absolute; 53 | content: ''; 54 | top: 100px; 55 | left: 50%; 56 | margin: -1.25em 0em 0em -1.25em; 57 | width: 2.5em; 58 | height: 2.5em; 59 | border-radius: 500rem; 60 | border: 0.2em solid rgba(0, 0, 0, 0.1); 61 | } 62 | .ui.tab.loading:after, 63 | .ui.tab.loading.segment:after { 64 | position: absolute; 65 | content: ''; 66 | top: 100px; 67 | left: 50%; 68 | margin: -1.25em 0em 0em -1.25em; 69 | width: 2.5em; 70 | height: 2.5em; 71 | -webkit-animation: button-spin 0.6s linear; 72 | animation: button-spin 0.6s linear; 73 | -webkit-animation-iteration-count: infinite; 74 | animation-iteration-count: infinite; 75 | border-radius: 500rem; 76 | border-color: #aaaaaa transparent transparent; 77 | border-style: solid; 78 | border-width: 0.2em; 79 | box-shadow: 0px 0px 0px 1px transparent; 80 | } 81 | 82 | 83 | /******************************* 84 | Tab Overrides 85 | *******************************/ 86 | 87 | 88 | 89 | /******************************* 90 | User Overrides 91 | *******************************/ 92 | 93 | -------------------------------------------------------------------------------- /minions/public/semantic/components/tab.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Tab 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.tab.loading.segment:after,.ui.tab.loading.segment:before,.ui.tab.loading:after,.ui.tab.loading:before{position:absolute;left:50%;content:'';top:100px;margin:-1.25em 0 0 -1.25em;width:2.5em}.ui.tab{display:none}.ui.tab.active,.ui.tab.open{display:block}.ui.tab.loading{position:relative;overflow:hidden;display:block;min-height:250px}.ui.tab.loading *{position:relative!important;left:-10000px!important}.ui.tab.loading.segment:before,.ui.tab.loading:before{height:2.5em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.tab.loading.segment:after,.ui.tab.loading:after{height:2.5em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#aaa transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent} -------------------------------------------------------------------------------- /minions/public/semantic/components/video.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Video 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | 12 | 13 | /******************************* 14 | Video 15 | *******************************/ 16 | 17 | .ui.video { 18 | background-color: #dddddd; 19 | position: relative; 20 | max-width: 100%; 21 | padding-bottom: 56.25%; 22 | height: 0px; 23 | overflow: hidden; 24 | } 25 | 26 | /*-------------- 27 | Content 28 | ---------------*/ 29 | 30 | 31 | /* Placeholder Image */ 32 | .ui.video .placeholder { 33 | background-color: #333333; 34 | } 35 | 36 | /* Play Icon Overlay */ 37 | .ui.video .play { 38 | cursor: pointer; 39 | position: absolute; 40 | top: 0px; 41 | left: 0px; 42 | z-index: 10; 43 | width: 100%; 44 | height: 100%; 45 | opacity: 0.8; 46 | -webkit-transition: opacity 0.3s; 47 | transition: opacity 0.3s; 48 | } 49 | .ui.video .play.icon:before { 50 | position: absolute; 51 | top: 50%; 52 | left: 50%; 53 | z-index: 11; 54 | background: rgba(0, 0, 0, 0.3); 55 | width: 8rem; 56 | height: 8rem; 57 | line-height: 8rem; 58 | border-radius: 500rem; 59 | color: #ffffff; 60 | font-size: 8rem; 61 | text-shadow: none; 62 | -webkit-transform: translateX(-50%) translateY(-50%); 63 | -ms-transform: translateX(-50%) translateY(-50%); 64 | transform: translateX(-50%) translateY(-50%); 65 | } 66 | .ui.video .placeholder { 67 | position: absolute; 68 | top: 0px; 69 | left: 0px; 70 | display: block; 71 | width: 100%; 72 | height: 100%; 73 | } 74 | 75 | /* IFrame Embed */ 76 | .ui.video .embed iframe, 77 | .ui.video .embed embed, 78 | .ui.video .embed object { 79 | position: absolute; 80 | border: none; 81 | width: 100%; 82 | height: 100%; 83 | top: 0px; 84 | left: 0px; 85 | margin: 0em; 86 | padding: 0em; 87 | } 88 | 89 | 90 | /******************************* 91 | States 92 | *******************************/ 93 | 94 | 95 | /*-------------- 96 | Hover 97 | ---------------*/ 98 | 99 | .ui.video .play:hover { 100 | opacity: 1; 101 | } 102 | 103 | /*-------------- 104 | Active 105 | ---------------*/ 106 | 107 | .ui.video.active .play, 108 | .ui.video.active .placeholder { 109 | display: none; 110 | } 111 | .ui.video.active .embed { 112 | display: inline; 113 | } 114 | 115 | 116 | /******************************* 117 | Video Overrides 118 | *******************************/ 119 | 120 | 121 | 122 | /******************************* 123 | Site Overrides 124 | *******************************/ 125 | 126 | -------------------------------------------------------------------------------- /minions/public/semantic/components/video.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 1.11.1 - Video 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.video{background-color:#ddd;position:relative;max-width:100%;padding-bottom:56.25%;height:0;overflow:hidden}.ui.video .placeholder{background-color:#333}.ui.video .play{cursor:pointer;position:absolute;top:0;left:0;z-index:10;width:100%;height:100%;opacity:.8;-webkit-transition:opacity .3s;transition:opacity .3s}.ui.video .play.icon:before{position:absolute;top:50%;left:50%;z-index:11;background:rgba(0,0,0,.3);width:8rem;height:8rem;line-height:8rem;border-radius:500rem;color:#fff;font-size:8rem;text-shadow:none;-webkit-transform:translateX(-50%)translateY(-50%);-ms-transform:translateX(-50%)translateY(-50%);transform:translateX(-50%)translateY(-50%)}.ui.video .placeholder{position:absolute;top:0;left:0;display:block;width:100%;height:100%}.ui.video .embed embed,.ui.video .embed iframe,.ui.video .embed object{position:absolute;border:none;width:100%;height:100%;top:0;left:0;margin:0;padding:0}.ui.video .play:hover{opacity:1}.ui.video.active .placeholder,.ui.video.active .play{display:none}.ui.video.active .embed{display:inline} -------------------------------------------------------------------------------- /minions/public/semantic/themes/basic/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/basic/assets/fonts/icons.eot -------------------------------------------------------------------------------- /minions/public/semantic/themes/basic/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/basic/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /minions/public/semantic/themes/basic/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/basic/assets/fonts/icons.woff -------------------------------------------------------------------------------- /minions/public/semantic/themes/default/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/default/assets/fonts/icons.eot -------------------------------------------------------------------------------- /minions/public/semantic/themes/default/assets/fonts/icons.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/default/assets/fonts/icons.otf -------------------------------------------------------------------------------- /minions/public/semantic/themes/default/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/default/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /minions/public/semantic/themes/default/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/default/assets/fonts/icons.woff -------------------------------------------------------------------------------- /minions/public/semantic/themes/default/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/default/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /minions/public/semantic/themes/default/assets/images/flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/public/semantic/themes/default/assets/images/flags.png -------------------------------------------------------------------------------- /minions/route.js: -------------------------------------------------------------------------------- 1 | var mockRouter = require('./routes/mockRouter.js'); 2 | var monitorRouter = require('./routes/monitorRouter.js'); 3 | 4 | module.exports = function (app) { 5 | app.use('/mock', mockRouter);// 6 | app.use('/m', monitorRouter); 7 | 8 | 9 | app.get("/", function (req, res, next) { 10 | res.render("index"); 11 | }); 12 | 13 | 14 | }; -------------------------------------------------------------------------------- /minions/routes/mockRouter.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var commonUtils = require("../lib/utils.js"); 3 | var logger = require("../lib/log.js").logger("mockRouter"); 4 | var config = require("../config"); 5 | var router = express.Router(); 6 | 7 | 8 | //生成token 9 | router.get("/genToken", function (req, res, next) { 10 | var userId = req.query.userId; 11 | var userName = req.query.userName; 12 | var appType = req.query.appType; 13 | 14 | console.log("params:", userId, userName, appType); 15 | 16 | var token1 = commonUtils.md5(userId+"_"+userName+"_"+appType, "token_gen_for_ticket@sumory.com"); 17 | var token2 = commonUtils.md5(userId+"_"+userName+"_"+appType+"_"+token1, "token_gen_for_spear@sumory.com"); 18 | 19 | res.json({ 20 | success:true, 21 | token1: token1, 22 | token2: token2 23 | }) 24 | }); 25 | 26 | 27 | module.exports = router; 28 | 29 | -------------------------------------------------------------------------------- /minions/routes/monitorRouter.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require("request"); 3 | 4 | var commonUtils = require("../lib/utils.js"); 5 | var redisUtils = require("../lib/redisUtils"); 6 | var zkUtils = require("../lib/zkUtils.js"); 7 | var logger = require("../lib/log.js").logger("monitorRouter"); 8 | var config = require("../config"); 9 | var router = express.Router(); 10 | 11 | 12 | router.get("/", function (req, res, next) { 13 | res.render("monitor"); 14 | }); 15 | 16 | //获取长连接服务节点 17 | router.get("/spears", function (req, res, next) { 18 | zkUtils.getSpears(function (err, spears) { 19 | if (err) { 20 | res.json([]); 21 | } 22 | else { 23 | res.json(spears); 24 | } 25 | }); 26 | }); 27 | 28 | //获取某个长连接服务节点下连接上的所有群组 29 | router.get("/:spear/groups", function (req, res, next) { 30 | var spear = req.params['spear']; 31 | redisUtils.keys("gru_stat:" + spear + "_*", function (err, result) { 32 | //console.log(result); 33 | res.json(result); 34 | }); 35 | }); 36 | 37 | //查询某个spear下的所有在线人数 38 | router.get("/:spear/usercount", function (req, res, next) { 39 | var spear = req.params['spear']; 40 | redisUtils.get("user_count:" + spear , function (err, result) { 41 | res.json(result); 42 | }); 43 | }); 44 | 45 | //获取整个长连接集群下所有的 【节点-群组】 对 46 | router.get("/groups", function (req, res, next) { 47 | redisUtils.keys("gru_stat:*", function (err, result) { 48 | console.log("redis上群组数目",result.length); 49 | res.json(result); 50 | }); 51 | }); 52 | 53 | 54 | //获取某个节点下某个群组的所有人 55 | router.get("/group/:id", function (req, res, next) { 56 | var id = req.params['id']; 57 | console.log("取组", id, "的统计信息"); 58 | 59 | var groupId = id.split("_")[1]; 60 | request.get({url:config.statServer+"/getGroupStat?groupId="+groupId}, function (error, response, body) { 61 | logger.info("请求statServer结果:", groupId, error, body); 62 | if(!error){ 63 | body = JSON.parse(body); 64 | if(body){ 65 | logger.info("请求statServer success"); 66 | res.json({ 67 | success:true, 68 | data:body 69 | }); 70 | }else{ 71 | logger.error("请求statServer结果出错"); 72 | res.json({ 73 | success:false 74 | }); 75 | } 76 | }else{ 77 | logger.error("请求statServer错误", error); 78 | res.json({ 79 | success:false 80 | }); 81 | } 82 | }); 83 | }); 84 | 85 | 86 | 87 | module.exports = router; 88 | 89 | -------------------------------------------------------------------------------- /minions/start_prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 'dev' or 'prod', check the folder config for more detail 4 | export NODE_ENV=prod 5 | 6 | echo 'Please be sure you have install all needed modules' 7 | 8 | 9 | echo 'start @prod mode' 10 | pm2 stop minions -f 11 | pm2 start app.js -n minions 12 | 13 | 14 | -------------------------------------------------------------------------------- /minions/start_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 'dev' or 'prod', check the folder config for more detail 4 | export NODE_ENV=test 5 | 6 | echo 'Please be sure you have install all needed modules' 7 | 8 | 9 | echo 'start @test mode' 10 | pm2 stop minions -f 11 | pm2 start app.js -n minions 12 | 13 | 14 | -------------------------------------------------------------------------------- /minions/test/redis.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var async = require('async'); 3 | 4 | function test() { 5 | var client = redis.createClient(); 6 | client.on("error", function(err) { 7 | console.log("Error " + err); 8 | }); 9 | 10 | client.on("ready", function() { 11 | 12 | var index = 0; 13 | var max = 1000000; 14 | var step = 30000; 15 | 16 | var interval = setInterval(function() { 17 | if (index >= max) { 18 | clearInterval(interval); 19 | } 20 | batchSome('test_key', index, index + step, max, function(c) { 21 | //console.log(c); 22 | }); 23 | index = index + step; 24 | 25 | }, 100); 26 | //最大11897115,0到11897114 27 | }); 28 | } 29 | 30 | 31 | process.on('uncaughtException', function(err) { 32 | console.log('Caught exception: ' + err); 33 | }); 34 | 35 | function batchSome(key, start, stop, max, callback) { 36 | var count = 0; 37 | for (var i = start; i < stop; i++) { 38 | client.hset(key, i, i, function(err, result) { 39 | if (err) console.log(result); 40 | count++; 41 | if (count % 10000 == 0) { 42 | console.log('current count:', count, ' start:', start, ' stop:', stop, ' i:', i); 43 | callback(count); 44 | } 45 | if (count == max) { 46 | console.log('finish: ', count); 47 | console.timeEnd('init'); 48 | } 49 | }); 50 | } 51 | } -------------------------------------------------------------------------------- /minions/test/utils.js: -------------------------------------------------------------------------------- 1 | var commonUtils = require("../lib/utils.js"); 2 | var crypto=require("crypto"); 3 | 4 | 5 | var s = commonUtils.md5("123456","-www"); 6 | console.log(s); 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /minions/views/common/header.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Gru Dashboard

4 |
5 |
6 |
7 | 11 |
12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /minions/views/common/meta.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /minions/views/common/sidebar.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumory/gru/89ff67b5941c3baf7c8cbf8a76e6293daaa8818c/minions/views/common/sidebar.ejs -------------------------------------------------------------------------------- /minions/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= msg %>

2 | -------------------------------------------------------------------------------- /spear-client/docs/nett-socket.io.bench.txt: -------------------------------------------------------------------------------- 1 | Customer feedback in 2012: 2 | CentOS, 1 CPU, 4GB RAM runned on VM: CPU 10%, Memory 15% 3 | 6000 xhr-long polling sessions or 15000 websockets sessions 4 | 4000 messages per second 5 | 6 | Customer feedback in 2014: 7 | 30 000 simultaneous websocket clients 8 | 140 000 messages per second 9 | less than 1 second average delay -------------------------------------------------------------------------------- /spear-client/src/main/java/com/sumory/gru/spear/client/pojo/ChatObject.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.client.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | public class ChatObject implements Serializable { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | private String userName; 12 | private String message; 13 | 14 | public ChatObject() { 15 | } 16 | 17 | public ChatObject(String userName, String message) { 18 | super(); 19 | this.userName = userName; 20 | this.message = message; 21 | } 22 | 23 | public String getUserName() { 24 | return userName; 25 | } 26 | 27 | public void setUserName(String userName) { 28 | this.userName = userName; 29 | } 30 | 31 | public String getMessage() { 32 | return message; 33 | } 34 | 35 | public void setMessage(String message) { 36 | this.message = message; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /spear-client/src/main/java/com/sumory/gru/spear/client/pojo/User.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.client.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.alibaba.fastjson.JSONObject; 6 | 7 | public class User implements Serializable { 8 | private static final long serialVersionUID = 1L; 9 | 10 | private long id; 11 | private long classId; 12 | private int type; 13 | private String name; 14 | 15 | public long getId() { 16 | return id; 17 | } 18 | 19 | public void setId(long id) { 20 | this.id = id; 21 | } 22 | 23 | public long getClassId() { 24 | return classId; 25 | } 26 | 27 | public void setClassId(long classId) { 28 | this.classId = classId; 29 | } 30 | 31 | public int getType() { 32 | return type; 33 | } 34 | 35 | public void setType(int type) { 36 | this.type = type; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public void setName(String name) { 44 | this.name = name; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return JSONObject.toJSONString(this); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spear-client/src/main/resources/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | BIN_DIR=`pwd` 4 | cd .. 5 | DEPLOY_DIR=`pwd` 6 | CONF_DIR=$DEPLOY_DIR/conf 7 | 8 | echo "curr dir is $DEPLOY_DIR" 9 | 10 | 11 | STDOUT_FILE=$DEPLOY_DIR/$1.log 12 | 13 | LIB_DIR=$DEPLOY_DIR/lib 14 | LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` 15 | 16 | JAVA_MEM_OPTS="" 17 | 18 | JAVA_MEM_OPTS=" -server -Xmx3g -Xms2g -Xmn1536m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " 19 | 20 | 21 | echo -e "Starting the spear client ...\c" 22 | nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.sumory.gru.spear.client.Main $1 $2 $3 $4 $5 > $STDOUT_FILE 2>&1 & 23 | 24 | echo "OK!" 25 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$DEPLOY_DIR" | awk '{print $2}'` 26 | echo "PID: $PIDS" 27 | echo $PIDS>pid""$4 28 | echo "STDOUT: $STDOUT_FILE" 29 | -------------------------------------------------------------------------------- /spear-client/src/main/resources/bin/start_msg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | BIN_DIR=`pwd` 4 | cd .. 5 | DEPLOY_DIR=`pwd` 6 | CONF_DIR=$DEPLOY_DIR/conf 7 | 8 | echo "curr dir is $DEPLOY_DIR" 9 | 10 | 11 | STDOUT_FILE=$DEPLOY_DIR/$1.log 12 | 13 | LIB_DIR=$DEPLOY_DIR/lib 14 | LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` 15 | 16 | JAVA_MEM_OPTS="" 17 | 18 | JAVA_MEM_OPTS=" -server -Xmx3g -Xms2g -Xmn1536m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " 19 | 20 | 21 | echo -e "Starting the spear client ...\c" 22 | nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.sumory.gru.spear.client.msg.MsgMain $1 $2 $3 > $STDOUT_FILE 2>&1 & 23 | 24 | echo "OK!" 25 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$DEPLOY_DIR" | awk '{print $2}'` 26 | echo "PID: $PIDS" 27 | echo $PIDS>pid""$4 28 | echo "STDOUT: $STDOUT_FILE" 29 | -------------------------------------------------------------------------------- /spear-client/src/main/resources/profiles/test/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout, file 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.Target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=[%d{dd HH:mm:ss,SSS\} %-5p] %-20c - %m%n 9 | 10 | log4j.appender.file = org.apache.log4j.DailyRollingFileAppender 11 | log4j.appender.file.File = logs/log.log 12 | log4j.appender.file.Append = true 13 | log4j.appender.file.Threshold = INFO 14 | log4j.appender.file.layout = org.apache.log4j.PatternLayout 15 | log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /spear-client/src/test/java/com/sumory/gru/spear/client/Test.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.client; 2 | 3 | 4 | public class Test { 5 | public static void main(String[] args) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spear/docs/nett-socket.io.bench.txt: -------------------------------------------------------------------------------- 1 | Customer feedback in 2012: 2 | CentOS, 1 CPU, 4GB RAM runned on VM: CPU 10%, Memory 15% 3 | 6000 xhr-long polling sessions or 15000 websockets sessions 4 | 4000 messages per second 5 | 6 | Customer feedback in 2014: 7 | 30 000 simultaneous websocket clients 8 | 140 000 messages per second 9 | less than 1 second average delay -------------------------------------------------------------------------------- /spear/docs/performance.txt: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | - 追求目标:支持横向扩展scale out 4 | 5 | 6 | ### 调优点 7 | 8 | - 集中互连系的client,智能路由到其他spear,这一点与无状态相悖,应在策略和代码上有所取舍 9 | - 用户退出时删除逻辑时间复杂度太高,每次都需要遍历,可更改为退出时单独标记不可用client,然后起另一个线程遍历删除client 10 | 其它用到遍历的地方也可考虑这个方案,比如分为runq,timeoutq,errorq等 11 | - 目前一个群退出的时候并没有清理整个map,考虑到分布处理了,很难判断班里的人是否全部退出,所以可以考虑采用超时机制 12 | - 杀掉服务端进程的时候,应该做善后处理,将连接释放掉 13 | 14 | ### 监控 15 | 16 | - 监控每个slot 17 | - 开启调优策略后监控slot变化 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /spear/docs/选型.md: -------------------------------------------------------------------------------- 1 | ### router、消息转发层选型 2 | 3 | nsq 4 | http://nsq.io/ 5 | 6 | nats 7 | https://nats.io/ 8 | java客户端 https://github.com/tyagihas/java_nats 9 | 10 | 内存方案 11 | netty-pipe 12 | 13 | rocketmq 14 | 15 | 16 | 以上方案的统一proxy,用于不同类型的项目部署,最小化安装等。 -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/domain/AuthObject.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.domain; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * 用于鉴权的object 7 | * 8 | * @author sumory.wu 9 | * @date 2015年3月12日 下午7:29:52 10 | */ 11 | public class AuthObject { 12 | private long id; 13 | private String name;//名称 14 | private String appType;//客户端标识 15 | private String token1;//鉴权用,由业务系统产生 16 | private String token2;//鉴权用,由ticket产生 17 | 18 | public long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(long id) { 23 | this.id = id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public String getToken1() { 35 | return token1; 36 | } 37 | 38 | public void setToken1(String token1) { 39 | this.token1 = token1; 40 | } 41 | 42 | public String getToken2() { 43 | return token2; 44 | } 45 | 46 | public void setToken2(String token2) { 47 | this.token2 = token2; 48 | } 49 | 50 | public String getAppType() { 51 | return appType; 52 | } 53 | 54 | public void setAppType(String appType) { 55 | this.appType = appType; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return JSON.toJSONString(this); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/domain/CommonResult.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.domain; 2 | 3 | import java.util.Map; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | 7 | /** 8 | * 返回结果通用数据结构 9 | * 10 | * @author sumory.wu 11 | * @date 2015年3月11日 下午4:17:43 12 | */ 13 | public class CommonResult { 14 | private boolean success;//是否成功 15 | private int errorCode;//错误状态码,一般情况下0表示正常 16 | private String msg;//描述信息 17 | private Map data;//返回数据,扩展字段 18 | 19 | public CommonResult(boolean success) { 20 | this(success, success ? ResultCode.SUCCESS : ResultCode.FAIL, "", null); 21 | } 22 | 23 | 24 | public CommonResult(boolean success, String msg) { 25 | this(success, success ? ResultCode.SUCCESS : ResultCode.FAIL, msg, null); 26 | } 27 | 28 | public CommonResult(boolean success, String msg, Map data) { 29 | this(success, success ? ResultCode.SUCCESS : ResultCode.FAIL, msg, data); 30 | } 31 | 32 | public CommonResult(boolean success, int errorCode, String msg) { 33 | this(success, errorCode, msg, null); 34 | } 35 | 36 | public CommonResult(boolean success, int errorCode, String msg, Map data) { 37 | super(); 38 | this.success = success; 39 | this.errorCode = errorCode; 40 | this.msg = (msg == null ? "" : msg); 41 | this.data = data; 42 | } 43 | 44 | public boolean isSuccess() { 45 | return success; 46 | } 47 | 48 | public void setSuccess(boolean success) { 49 | this.success = success; 50 | } 51 | 52 | public int getErrorCode() { 53 | return errorCode; 54 | } 55 | 56 | public void setErrorCode(int errorCode) { 57 | this.errorCode = errorCode; 58 | } 59 | 60 | public Map getData() { 61 | return data; 62 | } 63 | 64 | public void setData(Map data) { 65 | this.data = data; 66 | } 67 | 68 | public String getMsg() { 69 | return msg; 70 | } 71 | 72 | public void setMsg(String msg) { 73 | this.msg = msg; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return JSON.toJSONString(this); 79 | } 80 | 81 | public static void main(String[] args) { 82 | CommonResult cr = new CommonResult(true); 83 | System.out.println(cr); 84 | } 85 | 86 | } 87 | 88 | 89 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/domain/MsgObject.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.domain; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.alibaba.fastjson.JSON; 7 | 8 | /** 9 | * 传输的消息 10 | * 11 | *
12 |  * {
13 |  *     type: 1, //1 广播,0 单播
14 |  *     target: { // 给指定target
15 |  *         id: 10, //单播时指目标用户id,广播时指群组id
16 |  *         type: 1 //扩展字段,暂时无用
17 |  *     },
18 |  *     content: "字符串"//消息内容
19 |  * }
20 |  * 
21 |  * 
22 | * 23 | * @author sumory.wu 24 | * @date 2015年3月18日 下午5:40:50 25 | */ 26 | public class MsgObject { 27 | 28 | private long fromId;//发送者id 29 | private int type;//类型: 1 广播,0 单播给指定target 30 | private Map target; 31 | private String content; 32 | 33 | public static MsgType UNICAST = MsgType.UNICAST; 34 | public static MsgType BRAODCAST = MsgType.BRAODCAST; 35 | public static MsgType MULTICAST = MsgType.MULTICAST; 36 | 37 | public long getFromId() { 38 | return fromId; 39 | } 40 | 41 | public void setFromId(long fromId) { 42 | this.fromId = fromId; 43 | } 44 | 45 | public int getType() { 46 | return type; 47 | } 48 | 49 | public void setType(int type) { 50 | this.type = type; 51 | } 52 | 53 | public Map getTarget() { 54 | return target; 55 | } 56 | 57 | public void setTarget(Map target) { 58 | this.target = target; 59 | } 60 | 61 | public String getContent() { 62 | return content; 63 | } 64 | 65 | public void setContent(String content) { 66 | this.content = content; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return JSON.toJSONString(this); 72 | } 73 | 74 | public static void main(String[] args) { 75 | MsgObject o = new MsgObject(); 76 | Map target = new HashMap(); 77 | target.put("id", 10); 78 | target.put("type", 1); 79 | o.setType(0); 80 | o.setContent("send"); 81 | o.setTarget(target); 82 | 83 | System.out.println(o); 84 | 85 | System.out.println(MsgObject.BRAODCAST); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/domain/MsgType.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.domain; 2 | 3 | public enum MsgType { 4 | BRAODCAST(1), UNICAST(0), MULTICAST(2); 5 | 6 | private int value; 7 | 8 | MsgType(int v) { 9 | this.value = v; 10 | } 11 | 12 | public int getValue() { 13 | return this.value; 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/domain/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.domain; 2 | 3 | public class ResultCode { 4 | public final static int SUCCESS = 0;//成功 5 | public final static int FAIL = -1;//失败 6 | public final static int DEFAULT_ERROR = 1;//默认错误 7 | 8 | public final static int SYSTEM_ERROR = 1001;//系统错误 9 | public final static int LOGIC_ERROR = 1002;//逻辑错误 10 | public final static int PARAMS_ERROR = 1003;//参数错误 11 | } -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/domain/SubscribeObject.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.domain; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import com.alibaba.fastjson.JSON; 7 | 8 | /** 9 | * 用户订阅群组的object 10 | * 11 | * @author sumory.wu 12 | * @date 2015年10月18日 下午3:36:00 13 | */ 14 | public class SubscribeObject { 15 | private long userId; 16 | private List subscribeGroups; 17 | 18 | public long getUserId() { 19 | return userId; 20 | } 21 | 22 | public void setUserId(long userId) { 23 | this.userId = userId; 24 | } 25 | 26 | public List getSubscribeGroups() { 27 | return subscribeGroups; 28 | } 29 | 30 | public void setSubscribeGroups(List subscribeGroups) { 31 | this.subscribeGroups = subscribeGroups; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return JSON.toJSONString(this); 37 | } 38 | 39 | public class SubscribeGroup { 40 | private long id; 41 | private String name; 42 | private Map extra; 43 | 44 | public SubscribeGroup() { 45 | } 46 | 47 | public SubscribeGroup(long id, String name) { 48 | this.id = id; 49 | this.name = name; 50 | } 51 | 52 | public long getId() { 53 | return id; 54 | } 55 | 56 | public void setId(long id) { 57 | this.id = id; 58 | } 59 | 60 | public String getName() { 61 | return name; 62 | } 63 | 64 | public void setName(String name) { 65 | this.name = name; 66 | } 67 | 68 | public Map getExtra() { 69 | return extra; 70 | } 71 | 72 | public void setExtra(Map extra) { 73 | this.extra = extra; 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/extention/IAck.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.extention; 2 | 3 | public interface IAck { 4 | public void ack(T d); 5 | } 6 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/extention/LogAck.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.extention; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | 7 | public class LogAck implements IAck { 8 | private final static Logger logger = LoggerFactory.getLogger(LogAck.class); 9 | 10 | @Override 11 | public void ack(String d) { 12 | logger.info("Log Ack:{}", d); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/extention/RabbitMQAck.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.extention; 2 | 3 | import com.rabbitmq.client.Channel; 4 | import com.rabbitmq.client.QueueingConsumer; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | public class RabbitMQAck implements IAck { 9 | private final static Logger logger = LoggerFactory.getLogger(RabbitMQAck.class); 10 | 11 | private Channel channel; 12 | 13 | public RabbitMQAck(Channel ch) { 14 | this.channel = ch; 15 | } 16 | 17 | @Override 18 | public void ack(T d) { 19 | try { 20 | this.channel.basicAck(((QueueingConsumer.Delivery) d).getEnvelope().getDeliveryTag(), false); 21 | } catch (Exception e) { 22 | logger.error("RabbitMQ ack错误", e); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/extention/ReceiverDataListener.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.extention; 2 | 3 | import com.corundumstudio.socketio.listener.DataListener; 4 | import com.sumory.gru.spear.transport.IReceiver; 5 | 6 | public abstract class ReceiverDataListener implements DataListener { 7 | 8 | private IReceiver receiver; 9 | 10 | public ReceiverDataListener(IReceiver receiver) { 11 | this.receiver = receiver; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/extention/SenderDataListener.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.extention; 2 | 3 | import com.corundumstudio.socketio.listener.DataListener; 4 | import com.sumory.gru.spear.transport.ISender; 5 | 6 | public abstract class SenderDataListener implements DataListener { 7 | 8 | private ISender sender; 9 | 10 | public SenderDataListener(ISender sender) { 11 | this.sender = sender; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/message/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.message; 2 | 3 | 4 | /** 5 | * 基础消息类,可自行扩展
6 | * 后期考虑尽量减小消息体长度 7 | * 8 | * @author sumory.wu 9 | * @date 2015年2月4日 下午6:41:26 10 | */ 11 | public abstract class BaseMessage { 12 | public long id; 13 | public long createTime;//产生时间 14 | 15 | public transient long expireTime;//过期时间(绝对时间,非间隔) 16 | 17 | public BaseMessage(long id) { 18 | this.id = id; 19 | } 20 | 21 | public long getId() { 22 | return id; 23 | } 24 | 25 | public void setId(long id) { 26 | this.id = id; 27 | } 28 | 29 | public long getCreateTime() { 30 | return createTime; 31 | } 32 | 33 | public void setCreateTime(long createTime) { 34 | this.createTime = createTime; 35 | } 36 | 37 | public long getExpireTime() { 38 | return expireTime; 39 | } 40 | 41 | public void setExpireTime(long expireTime) { 42 | this.expireTime = expireTime; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/message/StringMessage.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.message; 2 | 3 | import java.util.Map; 4 | 5 | import com.alibaba.fastjson.JSONObject; 6 | 7 | // 字符串消息 8 | public class StringMessage extends BaseMessage { 9 | private long fromId;//来自谁 10 | private int msgType;//类型: 1 广播,0 单播给指定target 11 | private Map target; 12 | private String content; 13 | 14 | public StringMessage(long id, long fromId, int msgType, Map target, 15 | String content) { 16 | super(id); 17 | this.createTime = System.currentTimeMillis(); 18 | 19 | this.fromId = fromId; 20 | this.msgType = msgType; 21 | this.target = target; 22 | this.content = content; 23 | } 24 | 25 | public long getFromId() { 26 | return fromId; 27 | } 28 | 29 | public void setFromId(long fromId) { 30 | this.fromId = fromId; 31 | } 32 | 33 | public int getMsgType() { 34 | return msgType; 35 | } 36 | 37 | public void setMsgType(int msgType) { 38 | this.msgType = msgType; 39 | } 40 | 41 | public Map getTarget() { 42 | return target; 43 | } 44 | 45 | public void setTarget(Map target) { 46 | this.target = target; 47 | } 48 | 49 | public String getContent() { 50 | return content; 51 | } 52 | 53 | public void setContent(String content) { 54 | this.content = content; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return JSONObject.toJSONString(this); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/server/SpearServer.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.server; 2 | 3 | import com.corundumstudio.socketio.Configuration; 4 | import com.corundumstudio.socketio.SocketConfig; 5 | import com.corundumstudio.socketio.SocketIOServer; 6 | import com.sumory.gru.spear.SpearContext; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * 长连接接入server 12 | * 13 | * @author sumory.wu 14 | * @date 2015年10月17日 下午4:15:20 15 | */ 16 | public class SpearServer { 17 | private final static Logger logger = LoggerFactory.getLogger(SpearServer.class); 18 | 19 | private final SpearContext context; 20 | private SocketIOServer server; 21 | 22 | public SpearServer(final SpearContext context) { 23 | this.context = context; 24 | } 25 | 26 | public void run() { 27 | Configuration configuration = new Configuration(); 28 | String[] addrArray = this.context.getConfig().get("spear.addr").split(":"); 29 | configuration.setHostname(addrArray[0]); 30 | configuration.setPort(Integer.parseInt(addrArray[1])); 31 | 32 | SocketConfig socketConfig = new SocketConfig(); 33 | socketConfig.setTcpKeepAlive(true); 34 | socketConfig.setAcceptBackLog(1024);//accept队列长度,min(net.core.somaxconn, backlog) 35 | configuration.setSocketConfig(socketConfig); 36 | // setHeartbeatInterval Heartbeat interval (in seconds), defaults to 25 37 | // setHeartbeatTimeout Heartbeat timeout (in seconds), defaults to 60. Use 0 to disable it 38 | // setCloseTimeout Channel close timeout (in seconds) due to inactivity, defaults to 60 39 | 40 | server = new SocketIOServer(configuration); 41 | ActionListener actionListener = new ActionListener(context); 42 | server.addListeners(actionListener); 43 | server.start(); 44 | logger.info("SpearServer is running..."); 45 | 46 | //Configuration sConfiguration = server.getConfiguration(); 47 | //logger.info("bossThreads:{}, workerThreads:{}, socketConfig:{}", 48 | // sConfiguration.getBossThreads(), sConfiguration.getWorkerThreads(), 49 | // JSON.toJSONString(sConfiguration.getSocketConfig(), true)); 50 | 51 | } 52 | 53 | public void stop() { 54 | if (server != null) { 55 | server.stop(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/service/InnerIdService.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.sumory.gru.idgen.id.IdUtils; 7 | import com.sumory.gru.idgen.service.IdService; 8 | 9 | /** 10 | * 最简化部署single模式时使用的本地id生成服务 11 | * 12 | * @author sumory.wu 13 | * @date 2015年10月25日 下午8:38:09 14 | */ 15 | public class InnerIdService implements IdService { 16 | private final static Logger logger = LoggerFactory.getLogger(InnerIdService.class); 17 | 18 | @Override 19 | public String getServiceVersion() { 20 | return "inner"; 21 | } 22 | 23 | @Override 24 | public long getMsgId() { 25 | try { 26 | return IdUtils.genMsgId(); 27 | } 28 | catch (Exception e) { 29 | logger.error("inner id service generate id error", e); 30 | return Long.MIN_VALUE; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/service/InnerStatService.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.service; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import com.sumory.gru.common.domain.StatObject; 7 | import com.sumory.gru.stat.service.StatService; 8 | 9 | /** 10 | * 由于最简化部署时不提供stat功能,所以这里没有实现stat服务 11 | * 12 | * @author sumory.wu 13 | * @date 2015年10月25日 下午8:37:35 14 | */ 15 | public class InnerStatService implements StatService { 16 | 17 | @Override 18 | public String getServiceVersion() { 19 | return "inner"; 20 | } 21 | 22 | @Override 23 | public Set getGroupStat(long groupId) { 24 | return null; 25 | } 26 | 27 | @Override 28 | public List getGroupStatObjectList(long groupId) { 29 | return null; 30 | } 31 | 32 | @Override 33 | public void setGroupStatOfNode(String node, long groupId, String data, int seconds) { 34 | 35 | } 36 | 37 | @Override 38 | public void setGroupStatOfNode(List groupStats, int seconds) { 39 | 40 | } 41 | 42 | @Override 43 | public void setGroupStatObjectOfNode(String node, List stats, int seconds) { 44 | 45 | } 46 | 47 | @Override 48 | public void setUserCountOfNode(String node, String data, int seconds) { 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/thread/ExecutesManager.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.thread; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.Executor; 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | import java.util.concurrent.ThreadPoolExecutor; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * 13 | */ 14 | public class ExecutesManager { 15 | private ThreadPoolExecutor defaultExecutor = null; 16 | private final Map servicePoolCache = new HashMap(); 17 | 18 | // 19 | public ExecutesManager(int minCorePoolSize, int maxCorePoolSize, int queueSize, 20 | long keepAliveTime) { 21 | final BlockingQueue inWorkQueue = new LinkedBlockingQueue(queueSize); 22 | this.defaultExecutor = new ThreadPoolExecutor(minCorePoolSize, maxCorePoolSize,// 23 | keepAliveTime, TimeUnit.SECONDS, inWorkQueue,// 24 | new NameThreadFactory("GRU-SPEAR-%s"), new ThreadPoolExecutor.AbortPolicy()); 25 | } 26 | 27 | // 28 | public Executor getExecute(String serviceUniqueName) { 29 | if (this.servicePoolCache.isEmpty() == false) { 30 | ThreadPoolExecutor executor = this.servicePoolCache.get(serviceUniqueName); 31 | if (executor != null) { 32 | return executor; 33 | } 34 | } 35 | return this.defaultExecutor; 36 | } 37 | } -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/thread/NameThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.thread; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | 5 | /** 6 | * 7 | */ 8 | public class NameThreadFactory implements ThreadFactory { 9 | private String nameSample = "Thread-%s"; 10 | private int index = 1; 11 | 12 | public NameThreadFactory(String nameSample) { 13 | this.nameSample = nameSample; 14 | } 15 | 16 | public Thread newThread(Runnable run) { 17 | Thread t = new Thread(run); 18 | t.setName(String.format(nameSample, index++)); 19 | t.setDaemon(true); 20 | return t; 21 | } 22 | } -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/thread/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.thread; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /* 7 | * 线程池工厂类 8 | */ 9 | public class NamedThreadFactory implements ThreadFactory { 10 | 11 | static final AtomicInteger poolNumber = new AtomicInteger(1); 12 | 13 | final AtomicInteger threadNumber = new AtomicInteger(1); 14 | final ThreadGroup group; 15 | final String namePrefix; 16 | final boolean isDaemon; 17 | 18 | public NamedThreadFactory() { 19 | this("gru_named_thread_factory"); 20 | } 21 | 22 | public NamedThreadFactory(String name) { 23 | this(name, false); 24 | } 25 | 26 | @Override 27 | public Thread newThread(Runnable r) { 28 | Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); 29 | t.setDaemon(isDaemon); 30 | if (t.getPriority() != Thread.NORM_PRIORITY) { 31 | t.setPriority(Thread.NORM_PRIORITY); 32 | } 33 | return t; 34 | } 35 | 36 | public NamedThreadFactory(String preffix, boolean daemon) { 37 | SecurityManager s = System.getSecurityManager(); 38 | group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); 39 | namePrefix = preffix + "-" + poolNumber.getAndIncrement() + "-thread-"; 40 | isDaemon = daemon; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/transport/IReceiver.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.transport; 2 | 3 | public interface IReceiver { 4 | public void subscribe(final String topic); 5 | } 6 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/transport/ISender.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.transport; 2 | 3 | import com.sumory.gru.spear.domain.MsgObject; 4 | 5 | public interface ISender { 6 | 7 | public void run() throws Exception; 8 | 9 | public void send(final String topic, MsgObject msg); 10 | 11 | public void send(String topic, long tags, String body); 12 | } 13 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/transport/inner/InnerSender.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.transport.inner; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.sumory.gru.spear.SpearContext; 11 | import com.sumory.gru.spear.domain.MsgObject; 12 | import com.sumory.gru.spear.transport.ISender; 13 | 14 | /** 15 | * 单节点时发送器 16 | * 17 | * @author sumory.wu 18 | * @date 2015年10月15日 下午10:46:41 19 | */ 20 | public class InnerSender implements ISender { 21 | private final static Logger logger = LoggerFactory.getLogger(InnerSender.class); 22 | 23 | private SpearContext context; 24 | private BlockingQueue msgQueue;//存放消息的队列 25 | private ExecutorService executor; 26 | 27 | public InnerSender(final SpearContext context) { 28 | this.context = context; 29 | this.msgQueue = this.context.getMsgQueue(); 30 | this.executor = Executors.newFixedThreadPool(1); 31 | 32 | } 33 | 34 | @Override 35 | public void send(String topic, long tags, String body) { 36 | this.executor.execute(new Runnable() { 37 | @Override 38 | public void run() { 39 | try { 40 | MsgObject msg = new MsgObject(); 41 | 42 | InnerSender.this.msgQueue.put(msg);//put方法放入一个msg,若queue满了,等到queue有位置 43 | } 44 | catch (Exception e) { 45 | logger.error("往内部消息队列传入消息发生异常", e); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | @Override 52 | public void send(final String topic, final MsgObject msg) { 53 | this.executor.execute(new Runnable() { 54 | @Override 55 | public void run() { 56 | try { 57 | InnerSender.this.msgQueue.put(msg);//put方法放入一个msg,若queue满了,等到queue有位置 58 | } 59 | catch (Exception e) { 60 | logger.error("往内部消息队列传入消息发生异常", e); 61 | } 62 | } 63 | }); 64 | } 65 | 66 | @Override 67 | public void run() throws Exception { 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/transport/redis/RedisSender.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.transport.redis; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.sumory.gru.spear.SpearContext; 11 | import com.sumory.gru.spear.domain.MsgObject; 12 | import com.sumory.gru.spear.transport.ISender; 13 | 14 | /** 15 | * 单节点时发送器 16 | * 17 | * @author sumory.wu 18 | * @date 2015年10月15日 下午10:46:41 19 | */ 20 | public class RedisSender implements ISender { 21 | private final static Logger logger = LoggerFactory.getLogger(RedisSender.class); 22 | 23 | private SpearContext context; 24 | private BlockingQueue msgQueue;//存放消息的队列 25 | private ExecutorService executor; 26 | 27 | public RedisSender(final SpearContext context) { 28 | this.context = context; 29 | this.msgQueue = this.context.getMsgQueue(); 30 | this.executor = Executors.newScheduledThreadPool(1); 31 | 32 | } 33 | 34 | @Override 35 | public void send(String topic, long tags, String body) { 36 | 37 | } 38 | 39 | @Override 40 | public void send(final String topic, final MsgObject msg) { 41 | this.executor.execute(new Runnable() { 42 | @Override 43 | public void run() { 44 | try { 45 | RedisListener.getInstance().publish(topic, msg); 46 | } 47 | catch (Exception e) { 48 | logger.error("往redis消息队列{}传入消息发生异常", topic, e); 49 | } 50 | } 51 | }); 52 | } 53 | 54 | @Override 55 | public void run() throws Exception { 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/transport/rocketmq/RocketMQSender.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.transport.rocketmq; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.alibaba.rocketmq.client.producer.DefaultMQProducer; 7 | import com.alibaba.rocketmq.client.producer.SendResult; 8 | import com.alibaba.rocketmq.common.message.Message; 9 | import com.sumory.gru.idgen.service.IdService; 10 | import com.sumory.gru.spear.SpearContext; 11 | import com.sumory.gru.spear.domain.MsgObject; 12 | import com.sumory.gru.spear.transport.ISender; 13 | 14 | /** 15 | * mq消息发送器 16 | * 17 | * @author sumory.wu 18 | * @date 2015年3月19日 下午3:04:23 19 | */ 20 | public class RocketMQSender implements ISender { 21 | private final static Logger logger = LoggerFactory.getLogger(RocketMQSender.class); 22 | 23 | private SpearContext context; 24 | private DefaultMQProducer producer; 25 | private IdService idService; 26 | 27 | public RocketMQSender(final SpearContext context) { 28 | this.context = context; 29 | this.idService = context.getIdService(); 30 | //创建mq producer 31 | String mqNamesrvAddr = context.getConfig().get("mq.server.addr"); 32 | this.producer = new DefaultMQProducer("SpearProducer"); 33 | producer.setNamesrvAddr(mqNamesrvAddr); 34 | } 35 | 36 | public void run() throws Exception { 37 | this.producer.start(); 38 | } 39 | 40 | //使用groupId当tags 41 | public void send(String topic, long tags, String body) { 42 | long msgId = 0; 43 | try { 44 | msgId = this.idService.getMsgId(); 45 | } 46 | catch (Exception e) { 47 | logger.error("生成消息id异常", e); 48 | } 49 | 50 | try { 51 | Message msg = new Message(topic, tags + "", msgId + "", body.getBytes()); 52 | SendResult result = this.producer.send(msg); 53 | logger.debug("{} 发送消息到队列, topic:{} tags:{}, 返回 id:{} result:{}", Thread.currentThread() 54 | .getName(), topic, tags, result.getMsgId(), result.getSendStatus()); 55 | } 56 | catch (Exception e) { 57 | logger.error("Sender发送异常", e); 58 | } 59 | } 60 | 61 | @Override 62 | public void send(final String topic, final MsgObject msg) { 63 | //TODO 64 | } 65 | } 66 | 67 | //Message msg = new Message("PushTopic", "push", "1", "Just for test.".getBytes()); 68 | //SendResult result = producer.send(msg); 69 | //logger.debug("id:" + result.getMsgId() + " result:" + result.getSendStatus()); 70 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/zk/SpearNotifyListener.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.zk; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import org.apache.zookeeper.ZooDefs; 8 | import org.apache.zookeeper.ZooKeeper; 9 | import org.apache.zookeeper.data.Stat; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.sumory.gru.common.zk.NotifyListener; 14 | import com.sumory.gru.common.zk.ZkNode; 15 | 16 | public class SpearNotifyListener implements NotifyListener { 17 | private static final Logger logger = LoggerFactory.getLogger(SpearNotifyListener.class); 18 | private List zkNodes; 19 | 20 | public SpearNotifyListener(ZkNode... zkNodes) { 21 | if (zkNodes != null && zkNodes.length >= 1) 22 | this.zkNodes = Arrays.asList(zkNodes); 23 | else 24 | this.zkNodes = new ArrayList(); 25 | } 26 | 27 | @Deprecated 28 | private void addZkNode(ZkNode node) { 29 | this.zkNodes.add(node); 30 | } 31 | 32 | private boolean checkExists(ZooKeeper zk, String node) { 33 | try { 34 | Stat s = zk.exists(node, true); 35 | boolean exists = (s != null ? true : false); 36 | if (exists) 37 | logger.debug("{} 存在", node); 38 | else 39 | logger.debug("{} 不存在", node); 40 | 41 | return exists; 42 | } 43 | catch (Exception e) { 44 | logger.warn("checkExists异常 " + node, e); 45 | return false; 46 | } 47 | } 48 | 49 | /** 50 | * 在zk首次连接,或者其他情况导致重连(这个时候EPHEMERAL节点已消失)后,重新执行该操作 51 | */ 52 | @Override 53 | public void action(ZooKeeper zk) { 54 | synchronized (zk) { 55 | for (ZkNode n : zkNodes) { 56 | try { 57 | logger.info("检查节点:{}", n.getPath()); 58 | if (!checkExists(zk, n.getPath())) { 59 | logger.info("节点:{} 不存在,尝试创建{}", n.getPath(), n.toString()); 60 | zk.create(n.getPath(), n.getData(), ZooDefs.Ids.OPEN_ACL_UNSAFE, 61 | n.getCreateMode()); 62 | logger.info("尝试创建后检查节点{} 是否存在:{}", n.getPath(), 63 | checkExists(zk, n.getPath())); 64 | } 65 | } 66 | catch (Exception e) { 67 | logger.error("检查节点,不存在则创建,过程中出错", e); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/zk/SpearZkClientWatcher.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.zk; 2 | 3 | import java.io.IOException; 4 | 5 | import com.sumory.gru.common.zk.NotifyListener; 6 | import com.sumory.gru.common.zk.ZkClientWatcher; 7 | 8 | /** 9 | * 作为spear节点,需要与zk交互的功能并不多,只要保证节点正常启动时在zk始终保持一个临时节点即可 10 | * 如需要扩展,可在这里自定义对应事件,ps:注意zk的watch问题 11 | * 12 | * @author sumory.wu 13 | * @date 2015年3月13日 下午3:04:31 14 | */ 15 | public class SpearZkClientWatcher extends ZkClientWatcher { 16 | 17 | public SpearZkClientWatcher(String zkHost, String baseNode, int sessionTimeout, int retryTimes, 18 | NotifyListener listener) throws IOException { 19 | super(zkHost, baseNode, sessionTimeout, retryTimes, listener); 20 | } 21 | 22 | @Override 23 | protected void processDataChanged(String path) { 24 | // TODO Auto-generated method stub 25 | 26 | } 27 | 28 | @Override 29 | protected void processNodeChildrenChanged(String path) { 30 | // TODO Auto-generated method stub 31 | 32 | } 33 | 34 | @Override 35 | protected void processNodeCreated(String path) { 36 | // TODO Auto-generated method stub 37 | 38 | } 39 | 40 | @Override 41 | protected void processNodeDeleted(String path) { 42 | // TODO Auto-generated method stub 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /spear/src/main/java/com/sumory/gru/spear/zk/ZkUtil.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.zk; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.apache.zookeeper.CreateMode; 8 | import org.apache.zookeeper.KeeperException; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import com.sumory.gru.common.zk.NotifyListener; 13 | import com.sumory.gru.common.zk.ZkClientWatcher; 14 | import com.sumory.gru.common.zk.ZkNode; 15 | 16 | public class ZkUtil { 17 | private static final Logger logger = LoggerFactory.getLogger(ZkUtil.class); 18 | 19 | public static void getAllSpearNodes(String baseNode, ZkClientWatcher zkClientWatcher) 20 | throws KeeperException, InterruptedException, IOException { 21 | List nodes = zkClientWatcher.getZooKeeper().getChildren(baseNode, false); 22 | if (nodes != null) { 23 | for (String s : nodes) { 24 | System.out.println("子节点:" + baseNode + "/" + s); 25 | String node = new String(zkClientWatcher.getData(zkClientWatcher, baseNode + "/" 26 | + s)); 27 | System.out.println("子节点值:" + node); 28 | } 29 | } 30 | } 31 | 32 | public static void initNode(String zkHost, String baseNode, String outAddr, String spearId, 33 | int sessionTimeout, int retryTimes) throws Exception { 34 | 35 | //初始化listener 36 | String spearNode = baseNode + "/spear" + spearId; 37 | List zkNodes = new ArrayList(); 38 | zkNodes.add(new ZkNode(baseNode, "spear cluster nodes".getBytes(), CreateMode.PERSISTENT)); 39 | 40 | String spearNodeData = ("spear" + spearId) + "#http://" + outAddr;//例如:spear8#10.60.0.43:31001,严格遵守该格式,省去序列化麻烦 41 | zkNodes.add(new ZkNode(spearNode, spearNodeData.getBytes(), CreateMode.EPHEMERAL)); 42 | NotifyListener listener = new SpearNotifyListener( 43 | zkNodes.toArray(new ZkNode[zkNodes.size()])); 44 | 45 | //创建zookeeper 46 | ZkClientWatcher zkClientWatcher = new SpearZkClientWatcher(zkHost, baseNode, 47 | sessionTimeout, retryTimes, listener); 48 | 49 | getAllSpearNodes(baseNode, zkClientWatcher); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /spear/src/main/resources/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd `dirname $0` 4 | BIN_DIR=`pwd` 5 | cd .. 6 | DEPLOY_DIR=`pwd` 7 | CONF_DIR=$DEPLOY_DIR/conf 8 | 9 | echo "curr dir is $DEPLOY_DIR" 10 | 11 | 12 | SERVER_NAME=gru_spear 13 | 14 | STDOUT_FILE=$DEPLOY_DIR/stdout.log 15 | 16 | LIB_DIR=$DEPLOY_DIR/lib 17 | LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` 18 | 19 | JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " 20 | 21 | 22 | #RMI_SERVER_HOSTNAME=10.60.0.67 23 | #JMX_REMOTE_PORT=10900 24 | #JAVA_JMX_OPTS=" -Djava.rmi.server.hostname="$RMI_SERVER_HOSTNAME" -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port="$JMX_REMOTE_PORT" -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " 25 | JAVA_JMX_OPTS="" 26 | 27 | 28 | JAVA_MEM_OPTS=" -server -Xmx5g -Xms5g -Xmn2048m -XX:PermSize=128m -Xss256k -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " 29 | 30 | 31 | echo -e "Starting the $SERVER_NAME ...\c" 32 | nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.sumory.gru.spear.main.SpearMain > $STDOUT_FILE 2>&1 & 33 | 34 | 35 | echo "OK!" 36 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$DEPLOY_DIR" | awk '{print $2}'` 37 | echo "PID: $PIDS" 38 | echo $PIDS>pid 39 | echo "STDOUT: $STDOUT_FILE" 40 | -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/dev/dubbo.properties: -------------------------------------------------------------------------------- 1 | #################### dubbo #################################### 2 | dubbo.registry.client=curator 3 | dubbo.registry.address=zookeeper://192.168.100.183:2181 4 | dubbo.registry.file=/data/logs/spear/registry.cache 5 | 6 | 7 | ###### dubbo service api version ###### 8 | dubbo.gru.idgen.version=dev 9 | dubbo.gru.stat.version=dev 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/dev/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/spear 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/dev/system.properties: -------------------------------------------------------------------------------- 1 | #################### mode #################################### 2 | ###指的是节点间的通讯方式,有single、rocketmq、redis可选 3 | #single指单点模式,也是最小化安装模式,只需要一个spear节点提供服务,不需要idgen、stat、ticket等服务和zookeeper、redis、mq等中间件 4 | #redis指各个spear长连接服务节点间的消息传递通过redis实现 5 | #rocketmq指各个spear长连接节点间的消息传递通过rocketmq实现,rocketmq有很好的消息积压和传递性能 6 | mode=single 7 | 8 | #################### mq配置,在mode选择roketmq时应配置该值 ################################### 9 | mq.server.addr=10.60.0.47:9876 10 | 11 | #################### redis配置,在mode选择redis时需要通过redis转发消息,应配置该值 ################################### 12 | #最大分配的对象数 13 | redis.pool.maxTotal=200 14 | #最大能够保持idel状态的对象数 15 | redis.pool.maxIdle=30 16 | #当池内没有返回对象时,最大等待时间 ,单位毫秒 17 | redis.pool.maxWaitMillis=5000 18 | #向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。设为true,一个挂都不能用 19 | redis.pool.testOnBorrow=false 20 | #向连接池“归还”链接时,是否检测“链接”对象的有效性。 21 | redis.pool.testOnReturn=true 22 | #IP and Port 23 | redis.ip=192.168.100.185 24 | redis.port=6379 25 | 26 | 27 | 28 | #################### spear #################################### 29 | #长连接接入服务配置,集群中不同的spear节点,id都不能冲突,同机器上的port不能冲突 30 | spear.addr=0.0.0.0:31001 31 | spear.id=111 32 | #该节点对应的外部访问地址,需要上报到zk,由ticket模块负责对外暴露 33 | out.addr=0.0.0.0:31001 34 | 35 | #################### monitor ################################### 36 | #是否开启监控server, true启动,false不启动,监控的端口应该只对内网开放 37 | monitor.start=true 38 | monitor.addr=0.0.0.0:31002 39 | 40 | 41 | #################### ZooKeeper Properties ###################### 42 | zk.addr=192.168.100.183:2181 43 | zk.spear.cluster=/spearnodes_dev 44 | 45 | 46 | #################### stat 统计服务配置 #################################### 47 | #是否开启统计服务 48 | stat.start=true 49 | 50 | #################### hash盐,用于鉴权 ############################ 51 | auth.open=false 52 | salt.toticket=token_gen_for_ticket@sumory.com 53 | salt.tospear=token_gen_for_spear@sumory.com 54 | 55 | #################### 发出的消息的ack模式,即如何处理消息回执,目前支持LogAck ############# 56 | ack=log 57 | 58 | -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/test1/dubbo.properties: -------------------------------------------------------------------------------- 1 | #################### dubbo #################################### 2 | dubbo.registry.client=curator 3 | dubbo.registry.address=zookeeper://192.168.100.183:2181 4 | dubbo.registry.file=/data/logs/spear1/registry.cache 5 | 6 | ###### dubbo service api version ###### 7 | dubbo.gru.stat.version=1.0 8 | dubbo.gru.idgen.version=1.0 -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/test1/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/spear 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/test1/system.properties: -------------------------------------------------------------------------------- 1 | #################### mode #################################### 2 | ###指的是节点间的通讯方式,有single、rocketmq、redis可选 3 | #single指单点模式,也是最小化安装模式,只需要一个spear节点提供服务,不需要idgen、stat、ticket等服务和zookeeper、redis、mq等中间件 4 | #redis指各个spear长连接服务节点间的消息传递通过redis实现 5 | #rocketmq指各个spear长连接节点间的消息传递通过rocketmq实现,rocketmq有很好的消息积压和传递性能 6 | mode=redis 7 | 8 | #################### mq配置,在mode选择roketmq时应配置该值 ################################### 9 | mq.server.addr=10.60.0.47:9876 10 | 11 | #################### redis配置,在mode选择redis时需要通过redis转发消息,应配置该值 ################################### 12 | #最大分配的对象数 13 | redis.pool.maxTotal=200 14 | #最大能够保持idel状态的对象数 15 | redis.pool.maxIdle=30 16 | #当池内没有返回对象时,最大等待时间 ,单位毫秒 17 | redis.pool.maxWaitMillis=5000 18 | #向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。设为true,一个挂都不能用 19 | redis.pool.testOnBorrow=false 20 | #向连接池“归还”链接时,是否检测“链接”对象的有效性。 21 | redis.pool.testOnReturn=true 22 | #IP and Port 23 | redis.ip=192.168.100.185 24 | redis.port=6379 25 | 26 | #################### spear #################################### 27 | #长连接接入服务配置,集群中不同的spear节点,id都不能冲突,同机器上的port不能冲突 28 | spear.addr=192.168.100.122:31001 29 | spear.id=1 30 | #该节点对应的外部访问地址,需要上报到zk,由ticket模块负责对外暴露 31 | out.addr=192.168.100.122:31001 32 | 33 | #################### monitor ################################### 34 | #是否开启监控server, true启动,false不启动,监控的端口应该只对内网开放 35 | monitor.start=true 36 | monitor.addr=192.168.100.122:31002 37 | 38 | 39 | #################### ZooKeeper Properties ###################### 40 | zk.addr=192.168.100.183:2181 41 | zk.spear.cluster=/spearnodes_test 42 | 43 | 44 | #################### stat 统计服务配置 #################################### 45 | #是否开启统计服务 46 | stat.start=false 47 | 48 | #################### hash盐,用于鉴权 ############################ 49 | auth.open=true 50 | salt.toticket=token_gen_for_ticket@sumory.com 51 | salt.tospear=token_gen_for_spear@sumory.com 52 | 53 | #################### 发出的消息的ack模式,即如何处理消息回执 ############# 54 | ack=log -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/test2/dubbo.properties: -------------------------------------------------------------------------------- 1 | #################### dubbo #################################### 2 | dubbo.registry.client=curator 3 | dubbo.registry.address=zookeeper://192.168.100.183:2181 4 | dubbo.registry.file=/data/logs/spear2/registry.cache 5 | 6 | 7 | ###### dubbo service api version ###### 8 | dubbo.gru.stat.version=1.0 9 | dubbo.gru.idgen.version=1.0 -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/test2/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/spear 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spear/src/main/resources/profiles/test2/system.properties: -------------------------------------------------------------------------------- 1 | #################### mode #################################### 2 | ###指的是节点间的通讯方式,有single、rocketmq、redis可选 3 | #single指单点模式,也是最小化安装模式,只需要一个spear节点提供服务,不需要idgen、stat、ticket等服务和zookeeper、redis、mq等中间件 4 | #redis指各个spear长连接服务节点间的消息传递通过redis实现 5 | #rocketmq指各个spear长连接节点间的消息传递通过rocketmq实现,rocketmq有很好的消息积压和传递性能 6 | mode=redis 7 | 8 | #################### mq配置,在mode选择roketmq时应配置该值 ################################### 9 | mq.server.addr=10.60.0.47:9876 10 | 11 | #################### redis配置,在mode选择redis时需要通过redis转发消息,应配置该值 ################################### 12 | #最大分配的对象数 13 | redis.pool.maxTotal=200 14 | #最大能够保持idel状态的对象数 15 | redis.pool.maxIdle=30 16 | #当池内没有返回对象时,最大等待时间 ,单位毫秒 17 | redis.pool.maxWaitMillis=5000 18 | #向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。设为true,一个挂都不能用 19 | redis.pool.testOnBorrow=false 20 | #向连接池“归还”链接时,是否检测“链接”对象的有效性。 21 | redis.pool.testOnReturn=true 22 | #IP and Port 23 | redis.ip=192.168.100.185 24 | redis.port=6379 25 | 26 | #################### spear #################################### 27 | #长连接接入服务配置,集群中不同的spear节点,id都不能冲突,同机器上的port不能冲突 28 | spear.addr=192.168.100.122:31003 29 | spear.id=2 30 | #该节点对应的外部访问地址,需要上报到zk,由ticket模块负责对外暴露 31 | out.addr=192.168.100.122:31003 32 | 33 | #################### monitor ################################### 34 | #是否开启监控server, true启动,false不启动,监控的端口应该只对内网开放 35 | monitor.start=true 36 | monitor.addr=192.168.100.122:31004 37 | 38 | 39 | #################### ZooKeeper Properties ###################### 40 | zk.addr=192.168.100.183:2181 41 | zk.spear.cluster=/spearnodes_test 42 | 43 | 44 | #################### stat 统计服务配置 #################################### 45 | #是否开启统计服务 46 | stat.start=false 47 | 48 | #################### hash盐,用于鉴权 ############################ 49 | auth.open=true 50 | salt.toticket=token_gen_for_ticket@sumory.com 51 | salt.tospear=token_gen_for_spear@sumory.com 52 | 53 | #################### 发出的消息的ack模式,即如何处理消息回执 ############# 54 | ack=log 55 | -------------------------------------------------------------------------------- /spear/src/main/resources/spring/applicationContext-consumer-idgen.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /spear/src/main/resources/spring/applicationContext-consumer-stat.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /spear/src/main/resources/spring/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spear/src/test/java/com/sumory/gru/spear/Test.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | /** 8 | * 9 | * 10 | * @author sumory.wu 11 | * @date 2015年1月14日 下午8:25:27 12 | */ 13 | public class Test { 14 | public static void main(String[] args) { 15 | 16 | Integer[] abc = new Integer[] { 1, 2, 3, 4, 99, 7 }; 17 | List list = Arrays.asList(abc); 18 | Collections.sort(list); 19 | System.out.println(list.get(list.size() - 1)); 20 | 21 | List ol = null; 22 | for (Object o : ol) { 23 | System.out.println(o); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spear/src/test/java/com/sumory/gru/spear/mq/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.mq; 2 | 3 | import java.util.List; 4 | 5 | import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer; 6 | import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; 7 | import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; 8 | import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently; 9 | import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere; 10 | import com.alibaba.rocketmq.common.message.Message; 11 | import com.alibaba.rocketmq.common.message.MessageExt; 12 | 13 | public class Consumer { 14 | public static void main(String[] args) { 15 | DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("PushConsumer"); 16 | consumer.setNamesrvAddr("10.60.0.47:9876"); 17 | try { 18 | //订阅PushTopic下Tag为push的消息 19 | consumer.subscribe("class", "*"); 20 | //程序第一次启动从消息队列头取数据 21 | consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); 22 | consumer.registerMessageListener(new MessageListenerConcurrently() { 23 | public ConsumeConcurrentlyStatus consumeMessage(List list, 24 | ConsumeConcurrentlyContext Context) { 25 | Message msg = list.get(0); 26 | System.out.println(msg.toString()); 27 | return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; 28 | } 29 | }); 30 | consumer.start(); 31 | } 32 | catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /spear/src/test/java/com/sumory/gru/spear/mq/Producer.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.mq; 2 | 3 | import com.alibaba.rocketmq.client.producer.DefaultMQProducer; 4 | import com.alibaba.rocketmq.client.producer.SendResult; 5 | import com.alibaba.rocketmq.common.message.Message; 6 | 7 | public class Producer implements Runnable { 8 | private int count; 9 | private String name; 10 | 11 | public Producer(int count, String name) { 12 | this.count = count; 13 | this.name = name; 14 | } 15 | 16 | public static void main(String[] args) { 17 | Thread t1 = new Thread(new Producer(100, "t1")); 18 | Thread t2 = new Thread(new Producer(10000, "t2")); 19 | t1.start(); 20 | t2.start(); 21 | } 22 | 23 | @Override 24 | public void run() { 25 | DefaultMQProducer producer = new DefaultMQProducer(this.name); 26 | producer.setNamesrvAddr("10.60.0.47:9876"); 27 | long start = System.currentTimeMillis(); 28 | try { 29 | producer.start(); 30 | 31 | // Message msg = new Message("PushTopic", "push", "1", "Just for test.".getBytes()); 32 | // 33 | // SendResult result = producer.send(msg); 34 | // System.out.println("id:" + result.getMsgId() + " result:" + result.getSendStatus()); 35 | // 36 | // msg = new Message("PushTopic", "push", "2", "Just for test.".getBytes()); 37 | // 38 | // result = producer.send(msg); 39 | // System.out.println("id:" + result.getMsgId() + " result:" + result.getSendStatus()); 40 | // 41 | // msg = new Message("PullTopic", "pull", "1", "Just for test.".getBytes()); 42 | // 43 | // result = producer.send(msg); 44 | // System.out.println("id:" + result.getMsgId() + " result:" + result.getSendStatus()); 45 | 46 | for (int i = 0; i < this.count; i++) { 47 | String body = "from " + this.name + " just a test[" + i + "]"; 48 | Message msg = new Message("class", "10", "1", body.getBytes()); 49 | SendResult result = producer.send(msg); 50 | //System.out.println("id:" + result.getMsgId() + " result:" + result.getSendStatus()); 51 | } 52 | long end = System.currentTimeMillis(); 53 | System.out.println("finish" + (end - start)); 54 | } 55 | catch (Exception e) { 56 | e.printStackTrace(); 57 | } 58 | finally { 59 | producer.shutdown(); 60 | } 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /spear/src/test/java/com/sumory/gru/spear/zk/ZkTest.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.spear.zk; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.ZooDefs; 8 | import org.apache.zookeeper.ZooKeeper; 9 | 10 | import com.sumory.gru.common.config.Config; 11 | import com.sumory.gru.common.utils.DateUtil; 12 | import com.sumory.gru.common.zk.ZkClientWatcher; 13 | 14 | public class ZkTest { 15 | public static void main(String[] args) throws Exception { 16 | String zkHost = Config.get("zk.url"); 17 | String baseNode = Config.get("zk.spear.cluster"); 18 | int sessionTimeout = 3000; 19 | int retryTimes = 10; 20 | //创建zookeeper 21 | ZkClientWatcher zkClientWatcher = new SpearZkClientWatcher(zkHost, baseNode, 22 | sessionTimeout, retryTimes, null); 23 | ZooKeeper zk = zkClientWatcher.getZooKeeper(); 24 | 25 | zkClientWatcher.createEphemeralNode("/spearnodes/b", "spear-b".getBytes()); 26 | zk.create("/spearnodes/c", "spear-c".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, 27 | CreateMode.EPHEMERAL); 28 | //zk.close(); 29 | 30 | System.out.println("__________________________"); 31 | //while (true) { 32 | System.out.println(DateUtil.toDateTimeString(new Date())); 33 | List nodes = zk.getChildren(baseNode, true); 34 | if (nodes != null) { 35 | for (String s : nodes) { 36 | System.out.println("子节点:" + baseNode + "/" + s); 37 | String node = new String(zkClientWatcher.getData(zkClientWatcher, baseNode + "/" 38 | + s)); 39 | System.out.println("子节点值:" + node); 40 | } 41 | } 42 | System.out.println("++++++++++++++++++++++++++"); 43 | Thread.sleep(5000); 44 | 45 | //} 46 | 47 | Thread.sleep(Long.MAX_VALUE); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /stat/.gitignore: -------------------------------------------------------------------------------- 1 | /.apt_generated 2 | /target 3 | -------------------------------------------------------------------------------- /stat/src/main/assembly/package-stat-jar.xml: -------------------------------------------------------------------------------- 1 | 5 | dist 6 | 7 | jar 8 | 9 | false 10 | 11 | 12 | ${project.build.directory}/classes 13 | 14 | com/sumory/gru/stat/service/*.class 15 | 16 | / 17 | 18 | 19 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /stat/src/main/assembly/shared-jar-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | gru 6 | stat 7 | gru-stat-shared-jar 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /stat/src/main/java/com/sumory/gru/stat/context/StatContext.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.stat.context; 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue; 4 | 5 | /** 6 | * stat上下文 7 | * 8 | * @author sumory.wu 9 | * @date 2015年3月26日 上午11:49:25 10 | */ 11 | public class StatContext { 12 | 13 | private ConcurrentLinkedQueue spearNodes;//当前从zk获取的仍“存活”的spear节点 14 | 15 | private StatContext() { 16 | spearNodes = new ConcurrentLinkedQueue(); 17 | } 18 | 19 | private static class SingletonHolder { 20 | final static StatContext instance = new StatContext(); 21 | } 22 | 23 | public static StatContext getInstance() { 24 | return SingletonHolder.instance; 25 | } 26 | 27 | public ConcurrentLinkedQueue getSpearNodes() { 28 | return spearNodes; 29 | } 30 | 31 | public void setSpearNodes(ConcurrentLinkedQueue spearNodes) { 32 | this.spearNodes = spearNodes; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /stat/src/main/java/com/sumory/gru/stat/service/StatService.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.stat.service; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import com.sumory.gru.common.domain.StatObject; 7 | 8 | public interface StatService { 9 | public String getServiceVersion(); 10 | 11 | /** 12 | * 获取某个spear节点的群组统计信息:在线的user id列表 13 | * 14 | * @author sumory.wu @date 2015年3月26日 上午11:55:19 15 | * @param node 16 | * @param groupId 17 | * @return 18 | */ 19 | public Set getGroupStat(long groupId); 20 | 21 | /** 22 | * 获取某个spear节点的群组统计信息:详见StatObject 23 | * 24 | * @author sumory.wu @date 2015年4月20日 下午12:15:26 25 | * @param groupId 26 | * @return 27 | */ 28 | public List getGroupStatObjectList(long groupId); 29 | 30 | /** 31 | * 设置某spear节点上group的统计信息 32 | * 33 | * @author sumory.wu @date 2015年3月26日 上午11:56:25 34 | * @param node 来自的spear节点标识 35 | * @param groupId 群组id 36 | * @param seconds 存活时间 37 | */ 38 | public void setGroupStatOfNode(String node, long groupId, String data, int seconds); 39 | 40 | /** 41 | * 批量设置某spear节点上group的统计信息,减轻redis压力 42 | * 43 | * @author sumory.wu @date 2015年4月11日 下午1:40:28 44 | * @param groupStats 45 | * @param seconds 46 | */ 47 | public void setGroupStatOfNode(List groupStats, int seconds); 48 | 49 | /** 50 | * 批量设置某spear节点上group的统计信息,换用bitset,减小传输数据量,减轻redis压力 51 | * 52 | * @author sumory.wu @date 2015年4月20日 上午11:53:43 53 | * @param node 54 | * @param stats 55 | * @param seconds 56 | */ 57 | public void setGroupStatObjectOfNode(String node, List stats, int seconds); 58 | 59 | /** 60 | * 保存一个长连接服务节点用户数 61 | * 62 | * @author sumory.wu @date 2015年4月11日 下午1:41:15 63 | * @param node spear节点标识 64 | * @param data 此节点在线人数 65 | * @param seconds 在redis上的存活时间 66 | */ 67 | public void setUserCountOfNode(String node, String data, int seconds); 68 | } 69 | -------------------------------------------------------------------------------- /stat/src/main/java/com/sumory/gru/stat/servlet/User.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.stat.servlet; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.alibaba.fastjson.JSONObject; 7 | 8 | /** 9 | * 用户 10 | * 11 | * @author sumory.wu 12 | * @date 2015年3月13日 下午3:14:40 13 | */ 14 | public class User { 15 | private static Logger logger = LoggerFactory.getLogger(User.class); 16 | 17 | private long id; 18 | private long groupId; 19 | private String name; 20 | 21 | public User() { 22 | } 23 | 24 | public User(long id) { 25 | this.id = id; 26 | } 27 | 28 | public long getId() { 29 | return id; 30 | } 31 | 32 | public void setId(long id) { 33 | this.id = id; 34 | } 35 | 36 | public long getGroupId() { 37 | return groupId; 38 | } 39 | 40 | public void setGroupId(long groupId) { 41 | this.groupId = groupId; 42 | } 43 | 44 | public String getName() { 45 | return name; 46 | } 47 | 48 | public void setName(String name) { 49 | this.name = name; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return JSONObject.toJSONString(this); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /stat/src/main/java/com/sumory/gru/stat/zk/StatNotifyListener.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.stat.zk; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.ConcurrentLinkedQueue; 5 | 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.apache.zookeeper.KeeperException; 8 | import org.apache.zookeeper.KeeperException.NoNodeException; 9 | import org.apache.zookeeper.ZooKeeper; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.sumory.gru.common.zk.NotifyListener; 14 | import com.sumory.gru.stat.context.StatContext; 15 | 16 | public class StatNotifyListener implements NotifyListener { 17 | private static final Logger logger = LoggerFactory.getLogger(StatNotifyListener.class); 18 | 19 | private StatContext context; 20 | private String baseNode;//spear cluster的根目录 21 | 22 | public StatNotifyListener(String baseNode, StatContext context) { 23 | this.baseNode = baseNode; 24 | this.context = context; 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | @Override 31 | public void action(ZooKeeper zk) { 32 | logger.info("监听器action,需重新执行初始化spear存活列表"); 33 | List zkNodes = null; 34 | 35 | try { 36 | zkNodes = ZkUtil.getAllSpearNodes(this.getBaseNode(), zk); 37 | if (zkNodes != null && zkNodes.size() > 0) {//nodes节点不为空 38 | ConcurrentLinkedQueue spearNodes = this.context.getSpearNodes(); 39 | spearNodes.clear(); 40 | for (String zkNode : zkNodes) { 41 | logger.info("获得的spear节点:{},将其加入到存活列表中", zkNode); 42 | if (!StringUtils.isBlank(zkNode)) 43 | spearNodes.add(zkNode); 44 | } 45 | } 46 | } 47 | catch (KeeperException e) { 48 | if (e instanceof NoNodeException) {//节点下无子节点 49 | logger.warn(this.getBaseNode() + "下无子节点", e); 50 | } 51 | } 52 | catch (InterruptedException e) { 53 | logger.error("reset nodes发生异常", e); 54 | } 55 | catch (Exception e) { 56 | logger.error("reset nodes发生异常", e); 57 | } 58 | 59 | } 60 | 61 | public String getBaseNode() { 62 | return baseNode; 63 | } 64 | 65 | public void setBaseNode(String baseNode) { 66 | this.baseNode = baseNode; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /stat/src/main/java/com/sumory/gru/stat/zk/ZkUtil.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.stat.zk; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.KeeperException; 8 | import org.apache.zookeeper.ZooDefs.Ids; 9 | import org.apache.zookeeper.ZooKeeper; 10 | import org.apache.zookeeper.data.Stat; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import com.sumory.gru.common.zk.NotifyListener; 15 | import com.sumory.gru.common.zk.ZkClientWatcher; 16 | import com.sumory.gru.stat.context.StatContext; 17 | 18 | public class ZkUtil { 19 | private static final Logger logger = LoggerFactory.getLogger(ZkUtil.class); 20 | 21 | public static List getAllSpearNodes(String baseNode, ZooKeeper zk) 22 | throws KeeperException, InterruptedException, IOException { 23 | List nodes = zk.getChildren(baseNode, true); 24 | return nodes; 25 | } 26 | 27 | public static void watchNodes(String baseNode, ZooKeeper zk) throws KeeperException, 28 | InterruptedException, IOException { 29 | List nodes = zk.getChildren(baseNode, true); 30 | System.out.println("watch根节点:" + baseNode); 31 | zk.exists(baseNode, true); 32 | if (nodes != null) { 33 | for (String s : nodes) { 34 | System.out.println("watch子节点:" + baseNode + "/" + s); 35 | zk.exists(baseNode + "/" + s, true); 36 | } 37 | } 38 | } 39 | 40 | public static void initListener(String zkHost, String baseNode, int sessionTimeout, 41 | int retryTimes, StatContext context) throws Exception { 42 | //建立zk Node事件监听器 43 | NotifyListener listener = new StatNotifyListener(baseNode, context); 44 | //创建zookeeper 45 | ZkClientWatcher zkClientWatcher = new StatZkClientWatcher(zkHost, baseNode, sessionTimeout, 46 | retryTimes, listener, context); 47 | ZooKeeper zk = zkClientWatcher.getZooKeeper(); 48 | 49 | //判断根节点是否存在,不存在则建一个 50 | Stat spearRootNodeStat = zk.exists(baseNode, true); 51 | if (spearRootNodeStat == null) { 52 | zk.create(baseNode, "created by stat server".getBytes(), Ids.OPEN_ACL_UNSAFE, 53 | CreateMode.PERSISTENT); 54 | } 55 | 56 | //初始化时打印日志 57 | logger.info("查看stat server zk启动时已经存在的spear nodes:"); 58 | List ns = getAllSpearNodes(baseNode, zk); 59 | if (ns != null) { 60 | for (String n : ns) { 61 | logger.info(n); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /stat/src/main/resources/bin/dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source /etc/profile 3 | 4 | cd `dirname $0` 5 | BIN_DIR=`pwd` 6 | cd .. 7 | DEPLOY_DIR=`pwd` 8 | CONF_DIR=$DEPLOY_DIR/conf 9 | 10 | SERVER_NAME=`sed '/dubbo.application.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` 11 | LOGS_FILE=`sed '/dubbo.log4j.file/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` 12 | 13 | if [ -z "$SERVER_NAME" ]; then 14 | SERVER_NAME=`hostname` 15 | fi 16 | 17 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$CONF_DIR" |awk '{print $2}'` 18 | if [ -z "$PIDS" ]; then 19 | echo "ERROR: The $SERVER_NAME does not started!" 20 | exit 1 21 | fi 22 | 23 | LOGS_DIR="" 24 | if [ -n "$LOGS_FILE" ]; then 25 | LOGS_DIR=`dirname $LOGS_FILE` 26 | else 27 | LOGS_DIR=/data/logs/dubbo 28 | fi 29 | if [ ! -d $LOGS_DIR ]; then 30 | mkdir $LOGS_DIR 31 | fi 32 | DUMP_DIR=$LOGS_DIR/dump 33 | if [ ! -d $DUMP_DIR ]; then 34 | mkdir $DUMP_DIR 35 | fi 36 | DUMP_DATE=`date +%Y%m%d%H%M%S` 37 | DATE_DIR=$DUMP_DIR/$DUMP_DATE 38 | if [ ! -d $DATE_DIR ]; then 39 | mkdir $DATE_DIR 40 | fi 41 | 42 | echo -e "Dumping the $SERVER_NAME ...\c" 43 | for PID in $PIDS ; do 44 | jstack $PID > $DATE_DIR/jstack-$PID.dump 2>&1 45 | echo -e ".\c" 46 | jinfo $PID > $DATE_DIR/jinfo-$PID.dump 2>&1 47 | echo -e ".\c" 48 | jstat -gcutil $PID > $DATE_DIR/jstat-gcutil-$PID.dump 2>&1 49 | echo -e ".\c" 50 | jstat -gccapacity $PID > $DATE_DIR/jstat-gccapacity-$PID.dump 2>&1 51 | echo -e ".\c" 52 | jmap $PID > $DATE_DIR/jmap-$PID.dump 2>&1 53 | echo -e ".\c" 54 | jmap -heap $PID > $DATE_DIR/jmap-heap-$PID.dump 2>&1 55 | echo -e ".\c" 56 | jmap -histo $PID > $DATE_DIR/jmap-histo-$PID.dump 2>&1 57 | echo -e ".\c" 58 | if [ -r /usr/sbin/lsof ]; then 59 | /usr/sbin/lsof -p $PID > $DATE_DIR/lsof-$PID.dump 60 | echo -e ".\c" 61 | fi 62 | done 63 | if [ -r /bin/netstat ]; then 64 | /bin/netstat -an > $DATE_DIR/netstat.dump 2>&1 65 | echo -e ".\c" 66 | fi 67 | if [ -r /usr/bin/iostat ]; then 68 | /usr/bin/iostat > $DATE_DIR/iostat.dump 2>&1 69 | echo -e ".\c" 70 | fi 71 | if [ -r /usr/bin/mpstat ]; then 72 | /usr/bin/mpstat > $DATE_DIR/mpstat.dump 2>&1 73 | echo -e ".\c" 74 | fi 75 | if [ -r /usr/bin/vmstat ]; then 76 | /usr/bin/vmstat > $DATE_DIR/vmstat.dump 2>&1 77 | echo -e ".\c" 78 | fi 79 | if [ -r /usr/bin/free ]; then 80 | /usr/bin/free -t > $DATE_DIR/free.dump 2>&1 81 | echo -e ".\c" 82 | fi 83 | if [ -r /usr/bin/sar ]; then 84 | /usr/bin/sar > $DATE_DIR/sar.dump 2>&1 85 | echo -e ".\c" 86 | fi 87 | if [ -r /usr/bin/uptime ]; then 88 | /usr/bin/uptime > $DATE_DIR/uptime.dump 2>&1 89 | echo -e ".\c" 90 | fi 91 | echo "OK!" 92 | echo "DUMP: $DATE_DIR" 93 | -------------------------------------------------------------------------------- /stat/src/main/resources/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd `dirname $0` 4 | BIN_DIR=`pwd` 5 | cd .. 6 | DEPLOY_DIR=`pwd` 7 | CONF_DIR=$DEPLOY_DIR/conf 8 | 9 | echo "curr dir is $DEPLOY_DIR" 10 | 11 | 12 | SERVER_NAME=gru_stat 13 | 14 | STDOUT_FILE=$DEPLOY_DIR/stdout.log 15 | 16 | LIB_DIR=$DEPLOY_DIR/lib 17 | LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` 18 | 19 | JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " 20 | 21 | 22 | #RMI_SERVER_HOSTNAME=10.60.0.67 23 | #JMX_REMOTE_PORT=10900 24 | #JAVA_JMX_OPTS=" -Djava.rmi.server.hostname="$RMI_SERVER_HOSTNAME" -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port="$JMX_REMOTE_PORT" -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " 25 | JAVA_JMX_OPTS="" 26 | 27 | 28 | JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " 29 | 30 | 31 | echo -e "Starting the $SERVER_NAME ...\c" 32 | nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.sumory.gru.stat.main.StatMain > $STDOUT_FILE 2>&1 & 33 | 34 | 35 | echo "OK!" 36 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$DEPLOY_DIR" | awk '{print $2}'` 37 | echo "PID: $PIDS" 38 | echo $PIDS>pid 39 | echo "STDOUT: $STDOUT_FILE" 40 | -------------------------------------------------------------------------------- /stat/src/main/resources/bin/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | BIN_DIR=`pwd` 4 | cd .. 5 | DEPLOY_DIR=`pwd` 6 | CONF_DIR=$DEPLOY_DIR/conf 7 | 8 | SERVER_NAME=`sed '/dubbo.application.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` 9 | 10 | if [ -z "$SERVER_NAME" ]; then 11 | SERVER_NAME=`hostname` 12 | fi 13 | 14 | PIDS=`ps --no-heading -C java -f --width 1000 | grep "$CONF_DIR" |awk '{print $2}'` 15 | if [ -z "$PIDS" ]; then 16 | echo "ERROR: The $SERVER_NAME does not started!" 17 | exit 1 18 | fi 19 | 20 | if [ "$1" != "skip" ]; then 21 | $BIN_DIR/dump.sh 22 | fi 23 | 24 | echo -e "Stopping the $SERVER_NAME ...\c" 25 | for PID in $PIDS ; do 26 | kill $PID > /dev/null 2>&1 27 | done 28 | 29 | COUNT=0 30 | while [ $COUNT -lt 1 ]; do 31 | echo -e ".\c" 32 | sleep 1 33 | COUNT=1 34 | for PID in $PIDS ; do 35 | PID_EXIST=`ps --no-heading -p $PID` 36 | if [ -n "$PID_EXIST" ]; then 37 | COUNT=0 38 | break 39 | fi 40 | done 41 | done 42 | echo "OK!" 43 | echo "PID: $PIDS" 44 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/dev/dubbo.properties: -------------------------------------------------------------------------------- 1 | dubbo.container=spring 2 | dubbo.application.name=gru-provider-stat-service 3 | dubbo.spring.config=classpath*:applicationContext*.xml 4 | dubbo.protocol.name=dubbo 5 | dubbo.service.loadbalance=roundrobin 6 | 7 | 8 | dubbo.registry.client=curator 9 | dubbo.registry.address=zookeeper://192.168.100.183:2181 10 | dubbo.registry.file=/data/logs/stat/registry.cache 11 | dubbo.protocol.port=32000 12 | 13 | ###### dubbo service api version ###### 14 | dubbo.gru.stat.version=dev 15 | 16 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/dev/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/stat 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/dev/system.properties: -------------------------------------------------------------------------------- 1 | #################### zookeeper配置 #################### 2 | zk.addr=192.168.100.183:2181 3 | zk.spear.cluster=/spearnodes_dev 4 | 5 | #################### http配置 #################### 6 | http.port=50000 7 | 8 | #################### redis配置 ######################## 9 | 10 | #最大分配的对象数 11 | redis.pool.maxTotal=200 12 | #最大能够保持idel状态的对象数 13 | redis.pool.maxIdle=30 14 | #当池内没有返回对象时,最大等待时间 ,单位毫秒 15 | redis.pool.maxWaitMillis=5000 16 | #向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。设为true,一个挂都不能用 17 | redis.pool.testOnBorrow=false 18 | #向连接池“归还”链接时,是否检测“链接”对象的有效性。 19 | redis.pool.testOnReturn=true 20 | #IP and Port 21 | redis.ip=192.168.100.185 22 | redis.port=6379 23 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/test1/dubbo.properties: -------------------------------------------------------------------------------- 1 | dubbo.container=spring 2 | dubbo.application.name=gru-provider-stat-service 3 | dubbo.spring.config=classpath*:applicationContext*.xml 4 | dubbo.protocol.name=dubbo 5 | dubbo.service.loadbalance=roundrobin 6 | 7 | dubbo.registry.client=curator 8 | dubbo.registry.address=zookeeper://192.168.100.183:2181 9 | dubbo.registry.file=/data/logs/stat1/registry.cache 10 | dubbo.protocol.port=32001 11 | 12 | ###### dubbo service api version ###### 13 | dubbo.gru.stat.version=1.0 14 | 15 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/test1/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/stat 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/test1/system.properties: -------------------------------------------------------------------------------- 1 | #################### zookeeper配置 #################### 2 | zk.addr=192.168.100.183:2181 3 | zk.spear.cluster=/spearnodes_test 4 | 5 | #################### http配置 #################### 6 | http.port=50000 7 | 8 | #################### redis配置 ######################## 9 | 10 | #最大分配的对象数 11 | redis.pool.maxTotal=200 12 | #最大能够保持idel状态的对象数 13 | redis.pool.maxIdle=30 14 | #当池内没有返回对象时,最大等待时间 ,单位毫秒 15 | redis.pool.maxWaitMillis=5000 16 | #向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。设为true,一个挂都不能用 17 | redis.pool.testOnBorrow=false 18 | #向连接池“归还”链接时,是否检测“链接”对象的有效性。 19 | redis.pool.testOnReturn=true 20 | #IP #Port 21 | redis.ip=192.168.100.185 22 | redis.port=6379 23 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/test2/dubbo.properties: -------------------------------------------------------------------------------- 1 | dubbo.container=spring 2 | dubbo.application.name=gru-provider-stat-service 3 | dubbo.spring.config=classpath*:applicationContext*.xml 4 | dubbo.protocol.name=dubbo 5 | dubbo.service.loadbalance=roundrobin 6 | 7 | dubbo.registry.client=curator 8 | dubbo.registry.address=zookeeper://192.168.100.183:2181 9 | dubbo.registry.file=/data/logs/stat2/registry.cache 10 | dubbo.protocol.port=32002 11 | 12 | ###### dubbo service api version ###### 13 | dubbo.gru.stat.version=1.0 14 | 15 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/test2/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/stat 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /stat/src/main/resources/profiles/test2/system.properties: -------------------------------------------------------------------------------- 1 | #################### zookeeper配置 #################### 2 | zk.addr=192.168.100.183:2181 3 | zk.spear.cluster=/spearnodes_test 4 | 5 | #################### http配置 #################### 6 | http.port=50000 7 | 8 | #################### redis配置 ######################## 9 | 10 | #最大分配的对象数 11 | redis.pool.maxTotal=200 12 | #最大能够保持idel状态的对象数 13 | redis.pool.maxIdle=30 14 | #当池内没有返回对象时,最大等待时间 ,单位毫秒 15 | redis.pool.maxWaitMillis=5000 16 | #向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。设为true,一个挂都不能用 17 | redis.pool.testOnBorrow=false 18 | #向连接池“归还”链接时,是否检测“链接”对象的有效性。 19 | redis.pool.testOnReturn=true 20 | #IP #Port 21 | redis.ip=192.168.100.185 22 | redis.port=6379 23 | -------------------------------------------------------------------------------- /stat/src/main/resources/spring/applicationContext-provider-stat.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /stat/src/main/resources/spring/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /stat/src/test/java/com/sumory/gru/common/utils/RedisStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.common.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import com.sumory.gru.stat.redis.RedisStore; 6 | 7 | public class RedisStoreTest { 8 | 9 | @Test 10 | public void testList() { 11 | RedisStore store = RedisStore.getInstance(); 12 | 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ticket/src/main/java/com/sumory/gru/ticket/common/Node.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.ticket.common; 2 | 3 | /** 4 | * 节点基类 5 | * 6 | * @author sumory.wu 7 | * @date 2015年3月12日 下午2:44:13 8 | */ 9 | public class Node { 10 | String name; 11 | String addr;//需唯一 12 | 13 | public Node(String name, String addr) { 14 | this.name = name; 15 | this.addr = addr; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getAddr() { 27 | return addr; 28 | } 29 | 30 | public void setAddr(String addr) { 31 | this.addr = addr; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return this.name + "#" + this.addr; 37 | } 38 | } -------------------------------------------------------------------------------- /ticket/src/main/java/com/sumory/gru/ticket/domain/Ticket.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.ticket.domain; 2 | 3 | import java.util.Map; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | 7 | /** 8 | * 用户请求返回的结果实体 9 | * 10 | * @author sumory.wu 11 | * @date 2015年3月11日 下午4:17:43 12 | */ 13 | public class Ticket { 14 | private boolean success;//是否成功 15 | private int errorCode;//错误状态码 16 | private String msg;//描述信息 17 | 18 | private Map data;//返回数据 19 | 20 | public Ticket(boolean success, int errorCode, String msg, Map data) { 21 | super(); 22 | this.success = success; 23 | this.errorCode = errorCode; 24 | this.msg = (msg == null ? "" : msg); 25 | this.data = data; 26 | } 27 | 28 | public boolean isSuccess() { 29 | return success; 30 | } 31 | 32 | public void setSuccess(boolean success) { 33 | this.success = success; 34 | } 35 | 36 | public int getErrorCode() { 37 | return errorCode; 38 | } 39 | 40 | public void setErrorCode(int errorCode) { 41 | this.errorCode = errorCode; 42 | } 43 | 44 | public Map getData() { 45 | return data; 46 | } 47 | 48 | public void setData(Map data) { 49 | this.data = data; 50 | } 51 | 52 | public String getMsg() { 53 | return msg; 54 | } 55 | 56 | public void setMsg(String msg) { 57 | this.msg = msg; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return JSON.toJSONString(this); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /ticket/src/main/java/com/sumory/gru/ticket/main/TicketMain.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.ticket.main; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.sumory.gru.common.config.Config; 7 | import com.sumory.gru.ticket.common.Node; 8 | import com.sumory.gru.ticket.common.Shard; 9 | import com.sumory.gru.ticket.server.TicketServer; 10 | import com.sumory.gru.ticket.zk.ZkUtil; 11 | 12 | /** 13 | * ticket server启动入口 14 | * 15 | * @author sumory.wu 16 | * @date 2015年3月11日 上午9:41:00 17 | */ 18 | public class TicketMain { 19 | private static final Logger logger = LoggerFactory.getLogger(TicketMain.class); 20 | 21 | public static Shard shard = new Shard(); 22 | 23 | public static void main(String[] args) throws Exception { 24 | try { 25 | 26 | //启动zk监听 27 | String zkHost = Config.get("zk.addr"); 28 | String baseNode = Config.get("zk.spear.cluster"); 29 | int sessionTimeout = 3000; 30 | int retryTimes = 10; 31 | ZkUtil.initListener(zkHost, baseNode, sessionTimeout, retryTimes, shard); 32 | 33 | //启动ticket server接收http请求 34 | final TicketServer ns = new TicketServer(); 35 | ns.startHttp(); 36 | } 37 | catch (Exception e) { 38 | logger.error("启动ticket server异常", e); 39 | } 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /ticket/src/main/java/com/sumory/gru/ticket/zk/ZkUtil.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.ticket.zk; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.KeeperException; 8 | import org.apache.zookeeper.ZooDefs.Ids; 9 | import org.apache.zookeeper.ZooKeeper; 10 | import org.apache.zookeeper.data.Stat; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import com.sumory.gru.common.zk.NotifyListener; 15 | import com.sumory.gru.common.zk.ZkClientWatcher; 16 | import com.sumory.gru.ticket.common.Node; 17 | import com.sumory.gru.ticket.common.Shard; 18 | 19 | public class ZkUtil { 20 | private static final Logger logger = LoggerFactory.getLogger(ZkUtil.class); 21 | 22 | public static List getAllSpearNodes(String baseNode, ZooKeeper zk) 23 | throws KeeperException, InterruptedException, IOException { 24 | List nodes = zk.getChildren(baseNode, true); 25 | // if (nodes != null) { 26 | // for (String s : nodes) { 27 | // System.out.println("子节点:" + baseNode + "/" + s); 28 | // String node = new String(zk.getData(baseNode + "/" + s, true, null)); 29 | // System.out.println("子节点值:" + node); 30 | // } 31 | // } 32 | 33 | return nodes; 34 | } 35 | 36 | public static void watchNodes(String baseNode, ZooKeeper zk) throws KeeperException, 37 | InterruptedException, IOException { 38 | List nodes = zk.getChildren(baseNode, true); 39 | System.out.println("watch根节点:" + baseNode); 40 | zk.exists(baseNode, true); 41 | if (nodes != null) { 42 | for (String s : nodes) { 43 | System.out.println("watch子节点:" + baseNode + "/" + s); 44 | zk.exists(baseNode + "/" + s, true); 45 | } 46 | } 47 | } 48 | 49 | public static void initListener(String zkHost, String baseNode, int sessionTimeout, 50 | int retryTimes, Shard shard) throws Exception { 51 | 52 | //建立zk Node事件监听器 53 | NotifyListener listener = new TicketNotifyListener(baseNode, shard); 54 | 55 | //创建zookeeper 56 | ZkClientWatcher zkClientWatcher = new TicketZkClientWatcher(zkHost, baseNode, 57 | sessionTimeout, retryTimes, listener, shard); 58 | ZooKeeper zk = zkClientWatcher.getZooKeeper(); 59 | 60 | //判断根节点是否存在,不存在则建一个 61 | Stat spearRootNodeStat = zk.exists(baseNode, true); 62 | if (spearRootNodeStat == null) { 63 | zk.create(baseNode, "mydata".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 64 | } 65 | 66 | logger.info("查看ticket server zk启动时已经存在spear nodes"); 67 | List ns = getAllSpearNodes(baseNode, zk); 68 | if (ns != null) { 69 | for (String n : ns) { 70 | logger.info(n); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ticket/src/main/resources/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | BIN_DIR=`pwd` 4 | cd .. 5 | DEPLOY_DIR=`pwd` 6 | #配置文件目录 7 | CONF_DIR=$DEPLOY_DIR/conf 8 | #jar包目录 9 | LIB_DIR=$DEPLOY_DIR/lib 10 | 11 | echo "Current deploy dir is $DEPLOY_DIR" 12 | SERVER_NAME=gru_ticket 13 | 14 | #标准输出文件 15 | STDOUT_FILE=$DEPLOY_DIR/stdout.log 16 | #GC日志文件 17 | GC_LOG_FILE=$DEPLOY_DIR/gc.log 18 | 19 | 20 | #jar路径组装成classpath格式 21 | LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` 22 | 23 | #java启动参数 24 | JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " 25 | #JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=60000 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " 26 | JAVA_JMX_OPTS="" 27 | JAVA_MEM_OPTS=" -server -Xms1g -Xmx1g -Xmn300m -Xss256k -XX:PermSize=128m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " 28 | JAVA_GC_OPTS=" -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintClassHistogram -XX:-TraceClassUnloading -verbose:gc -Xloggc:"$GC_LOG_FILE 29 | 30 | 31 | echo "$SERVER_NAME starting..." 32 | nohup java $JAVA_OPTS $JAVA_JMX_OPTS $JAVA_MEM_OPTS $JAVA_GC_OPTS -classpath $CONF_DIR:$LIB_JARS com.sumory.gru.ticket.main.TicketMain > $STDOUT_FILE 2>&1 & 33 | PIDS=`ps aux | grep java | grep "$DEPLOY_DIR" | grep -v grep | awk '{print $2}'` 34 | echo "$SERVER_NAME started PID: $PIDS" 35 | echo "Please check log files" -------------------------------------------------------------------------------- /ticket/src/main/resources/bin/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | BIN_DIR=`pwd` 4 | cd .. 5 | DEPLOY_DIR=`pwd` 6 | CONF_DIR=$DEPLOY_DIR/conf 7 | 8 | SERVER_NAME=gru_ticket 9 | 10 | 11 | PIDS=`ps aux | grep java | grep "$CONF_DIR" | grep -v grep |awk '{print $2}'` 12 | if [ -z "$PIDS" ]; then 13 | echo "ERROR: The $SERVER_NAME does not started!" 14 | exit 1 15 | fi 16 | 17 | echo "Stopping $SERVER_NAME..." 18 | for PID in $PIDS ; do 19 | echo "kill PID: "$PID 20 | kill -15 $PID > /dev/null 2>&1 21 | done 22 | 23 | sleep 4s 24 | 25 | echo "OK!" 26 | echo "PID: $PIDS" -------------------------------------------------------------------------------- /ticket/src/main/resources/profiles/dev/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/ticket 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ticket/src/main/resources/profiles/dev/system.properties: -------------------------------------------------------------------------------- 1 | #################### 长连接接入服务配置 ############################ 2 | ticket.hostname=0.0.0.0 3 | ticket.port=30000 4 | 5 | #################### ZooKeeper Properties ###################### 6 | zk.addr=192.168.100.183:2181 7 | zk.spear.cluster=/spearnodes_dev 8 | 9 | 10 | #################### hash盐,用于鉴权 ############################ 11 | auth.open=false 12 | #请求ticket服务时,如果auth.open设为true,则需要使用下面两个salt来判断请求的参数是否正确,并生成新token 13 | salt.toticket=token_gen_for_ticket@sumory.com 14 | salt.tospear=token_gen_for_spear@sumory.com -------------------------------------------------------------------------------- /ticket/src/main/resources/profiles/test1/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/ticket 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ticket/src/main/resources/profiles/test1/system.properties: -------------------------------------------------------------------------------- 1 | #长连接接入服务配置 2 | ticket.hostname=192.168.100.122 3 | ticket.port=30001 4 | 5 | #################### ZooKeeper Properties ###################### 6 | zk.addr=192.168.100.183:2181 7 | zk.spear.cluster=/spearnodes_test 8 | 9 | 10 | #################### hash盐,用于鉴权 ############################ 11 | auth.open=true 12 | #请求ticket服务时,如果auth.open设为true,则需要使用下面两个salt来判断请求的参数是否正确,并生成新token 13 | salt.toticket=token_gen_for_ticket@sumory.com 14 | salt.tospear=token_gen_for_spear@sumory.com -------------------------------------------------------------------------------- /ticket/src/main/resources/profiles/test2/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /data/logs/ticket 5 | [%d{MM-dd HH:mm:ss.SSS}] [%p] %l - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ticket/src/main/resources/profiles/test2/system.properties: -------------------------------------------------------------------------------- 1 | #长连接接入服务配置 2 | ticket.hostname=192.168.100.122 3 | ticket.port=30002 4 | 5 | #################### ZooKeeper Properties ###################### 6 | zk.addr=192.168.100.183:2181 7 | zk.spear.cluster=/spearnodes_test 8 | 9 | 10 | #################### hash盐,用于鉴权 ############################ 11 | auth.open=true 12 | #请求ticket服务时,如果auth.open设为true,则需要使用下面两个salt来判断请求的参数是否正确,并生成新token 13 | salt.toticket=token_gen_for_ticket@sumory.com 14 | salt.tospear=token_gen_for_spear@sumory.com -------------------------------------------------------------------------------- /ticket/src/test/java/com/sumory/gru/ticket/common/test/MockSpearNode.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.ticket.common.test; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.ZooDefs; 8 | import org.apache.zookeeper.ZooKeeper; 9 | 10 | import com.sumory.gru.common.config.Config; 11 | import com.sumory.gru.common.utils.DateUtil; 12 | import com.sumory.gru.common.zk.ZkClientWatcher; 13 | import com.sumory.gru.ticket.zk.TicketZkClientWatcher; 14 | 15 | public class MockSpearNode { 16 | public static void main(String[] args) throws Exception { 17 | String zkHost = Config.get("zk.url"); 18 | String baseNode = Config.get("zk.spear.cluster"); 19 | int sessionTimeout = 3000; 20 | int retryTimes = 10; 21 | //创建zookeeper 22 | ZkClientWatcher zkClientWatcher = new TicketZkClientWatcher(zkHost, baseNode, 23 | sessionTimeout, retryTimes, null, null); 24 | ZooKeeper zk = zkClientWatcher.getZooKeeper(); 25 | 26 | zkClientWatcher.createEphemeralNode("/spearnodes/1", "spear1#22.1.2.3:1000".getBytes()); 27 | zk.create("/spearnodes/2", "spear2#15.168.1.122:4000".getBytes(), 28 | ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); 29 | //zk.close(); 30 | 31 | System.out.println("__________________________"); 32 | //while (true) { 33 | System.out.println(DateUtil.toDateTimeString(new Date())); 34 | List nodes = zk.getChildren(baseNode, true); 35 | if (nodes != null) { 36 | for (String s : nodes) { 37 | System.out.println("子节点:" + baseNode + "/" + s); 38 | String node = new String(zkClientWatcher.getData(zkClientWatcher, baseNode + "/" 39 | + s)); 40 | System.out.println("子节点值:" + node); 41 | } 42 | } 43 | System.out.println("++++++++++++++++++++++++++"); 44 | 45 | //} 46 | 47 | Thread.sleep(Long.MAX_VALUE); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ticket/src/test/java/com/sumory/gru/ticket/common/test/ZooKeeperTest.java: -------------------------------------------------------------------------------- 1 | package com.sumory.gru.ticket.common.test; 2 | 3 | import org.apache.zookeeper.CreateMode; 4 | import org.apache.zookeeper.WatchedEvent; 5 | import org.apache.zookeeper.Watcher; 6 | import org.apache.zookeeper.ZooDefs.Ids; 7 | import org.apache.zookeeper.ZooKeeper; 8 | import org.apache.zookeeper.data.Stat; 9 | 10 | public class ZooKeeperTest { 11 | public static void main(String[] args) throws Exception { 12 | Watcher wh = new Watcher() { 13 | public void process(WatchedEvent event) { 14 | System.out.println("回调watcher实例: 路径" + event.getPath() + " 类型:" + event.getType()); 15 | 16 | } 17 | }; 18 | 19 | ZooKeeper zk = new ZooKeeper("10.60.0.43:2181", 500000, wh); 20 | System.out.println("---------------------"); 21 | 22 | // 创建一个节点root,数据是mydata,不进行ACL权限控制,节点为永久性的(即客户端shutdown了也不会消失) 23 | 24 | Stat s1 = zk.exists("/spearnodes", true); 25 | 26 | if (s1 == null) 27 | zk.create("/spearnodes", "mydata".getBytes(), Ids.OPEN_ACL_UNSAFE, 28 | CreateMode.PERSISTENT); 29 | 30 | System.out.println("---------------------"); 31 | 32 | // 在root下面创建一个childone znode,数据为childone,不进行ACL权限控制,节点为永久性的 33 | 34 | Stat s = zk.exists("/spearnodes/childone", true); 35 | if (s != null) { 36 | zk.delete("/spearnodes/childone", -1); 37 | } 38 | zk.create("/spearnodes/childone", "childone".getBytes(), Ids.OPEN_ACL_UNSAFE, 39 | CreateMode.PERSISTENT); 40 | 41 | System.out.println("---------------------"); 42 | 43 | // 删除/spearnodes/childone这个节点,第二个参数为版本,-1的话直接删除,无视版本 44 | 45 | zk.exists("/spearnodes/childone", true); 46 | zk.delete("/spearnodes/childone", -1); 47 | 48 | System.out.println("---------------------"); 49 | 50 | zk.exists("/spearnodes", true); 51 | zk.delete("/spearnodes", -1); 52 | 53 | System.out.println("---------------------"); 54 | 55 | // 关闭session 56 | 57 | zk.close(); 58 | } 59 | } 60 | --------------------------------------------------------------------------------