由于现在做的项目的业务需要,需要后台服务器,主动给移动端推送,任务,数据库是mysql,刚开始是轮询,后来想高大上,弄个主动推送,用netty做。
检测数据变化,实现推送,用心跳频率,来检测任务状态变化,然后推送,遭到否决,不及时。
轮询,对服务器性能消耗大,之前为了解决这个问题,ios和android通一套代码整了两个tcp服务端口,改进方法,用redis在其他系统中记录有变化的任务,生成,其他的对任务的修改,然后在tcp第一次连接的时候查询客户的任务记录记录有连接channel的message中
Redis队列功能介绍
List
常用命令:
Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用
Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用
Brpoplpush
Lindex获取一个元素,通过其索引列表
Linsert在列表中的另一个元素之前或之后插入一个元素
Llen获得队列(List)的长度
Lpop从队列的左边出队一个元素
Lpush从队列的左边入队一个或多个元素
Lpushx当队列存在时,从队到左边入队一个元素
Lrange从列表中获取指定返回的元素
Lrem从列表中删除元素
Lset设置队列里面一个元素的值
Ltrim修剪到指定范围内的清单
Rpop从队列的右边出队一个元素
Rpoplpush删除列表中的最后一个元素,将其追加到另一个列表
Rpush从队列的右边入队一个元素
Rpushx从队列的右边入队一个元素,仅队列存在时有效
代码:
public class TaskPush implements Runnable { private final static Logger logger = LoggerFactory.getLogger(TaskPush.class); //业务线程处理数据库业务 ExecutorService threadPool = Executors.newCachedThreadPool(); private static TaskMapper taskMapper; static { // 获取spring上下文,使用静态代码块初始化 appContext = new ClassPathXmlApplicationContext(new String[]{"classpath:spring-ios.xml"}); } public TaskPush() { } @Override public void run() { //用线程处理业务 while (true){ //遍历所有的会话 try{ Map<String, TcpMessage> userList= ChannelManager.userList; //判断是否为空 if(userList.isEmpty()){ //System.out.println("now is empty"); //如果现在还没有客户端连接服务器 线程沉睡500 //Thread.sleep(500); }else { for(String s: userList.keySet()){ TcpMessage message= ChannelManager.userList.get(s); //做一次自我清理 if(!message.getCtx().channel().isActive()){ ChannelManager.userList.remove(s); continue; } if(s.indexOf(ClientType.SS.name()) >-1){ //查询缓存中的任务做判断 TaskDO rd=constantRedisDao.queryTcpTask(s); //查询原来任务 TaskDO ld=(TaskDO)message.getObject(); if(rd != null){ if(ld == null){//司机接受任务 //加入线程池,推送缓存中的任务 if(rd.isActive()) threadPool.submit(new MyTask(message,rd)); }else { //与原来的任务做比较,任务id和任务状态 long rdid=rd.getId(); long ldid=ld.getId(); int rds=rd.getStatus(); int lds=ld.getStatus(); int rv= rd.getVersion(); int lv=ld.getVersion(); if(rdid==ldid && rds==lds && rv==lv){ //只有通一个任务,并且状态一样才不会推送 //System.out.println("do nothing !!!"); }else { //加入线程池,推送缓存中的任务 //第一种,不是同一个任务,说明刚连接tcp而且有任务.推送新任务 if(rdid == ldid){ threadPool.submit(new MyTask(message,rd)); }else { threadPool.submit(new MyTask(message,ld)); } //threadPool.submit(new MyTask(message,rd)); } } //更新任务 message.setObject(rd); ChannelManager.userList.put(s,message); } } } } //Thread.sleep(50); }catch (Exception e){ e.printStackTrace(); } } } public static void main(String[] args) { } class MyTask implements Runnable{ private TcpMessage message; private TaskDO taskDO; public MyTask(TcpMessage message, TaskDO taskDO) { this.message = message; this.taskDO = taskDO; } @Override public void run() { synchronized (Object.class){ try{ //推送消息生成省略 message.getCtx().writeAndFlush(tcpResponseBuilder.build()); }catch (Exception e){ e.printStackTrace(); } } } } }
public class TcpMessage { //逻辑code private int code; //信息分类 private String useType; //id private long id; //ctx -->socketchannel private ChannelHandlerContext ctx; //用到的信息比如任务,或者用户订单 private Object object; public TcpMessage() { } public TcpMessage(String useType, long id, ChannelHandlerContext ctx, Object object) { this.useType = useType; this.id = id; this.ctx = ctx; this.object = object; } public TcpMessage(int code, String useType, long id, ChannelHandlerContext ctx, Object object) { this.code = code; this.useType = useType; this.id = id; this.ctx = ctx; this.object = object; } public Object getObject() { return object; } public TcpMessage setObject(Object object) { this.object = object; return this; } public int getCode() { return code; } public TcpMessage setCode(int code) { this.code = code; return this; } public String getUseType() { return useType; } public TcpMessage setUseType(String useType) { this.useType = useType; return this; } public long getId() { return id; } public TcpMessage setId(long id) { this.id = id; return this; } public ChannelHandlerContext getCtx() { return ctx; } public TcpMessage setCtx(ChannelHandlerContext ctx) { this.ctx = ctx; return this; } }
这样只是 简单的实现了,信息推送。下面要解决,信息发送后没接收到,多端改变任务的时候,因推送不及时,就会在rendis中被新修改的任务覆盖掉,造成漏发,考虑用队列来做,不能只是简单的存入redis一个任务,再拿出来做比较
改进用redis做队列,多对一,制造和消费,多制造,单消费,符合
借鉴例子
redis 命令:来自http://www.cnblogs.com/ztteng/p/4073211.html
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class RedisCur { public static void main(String[] args) { List<Future> futures = new ArrayList<Future>(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { futures.add(executorService.submit(new WriteRedis(String.valueOf(i)))); } } } public class WriteRedis implements Runnable { private String value; /** * @param value */ public WriteRedis(String value) { super(); this.value = value; } public void run(){ Jedis jedisClient = new Jedis("127.0.0.1", 6379); try { String threadName = Thread.currentThread().getName(); jedisClient.lpush("LOCK", value); System.err.println(threadName + "-" + value); } catch (Exception e) { e.printStackTrace(); } } }
public class RedisListener { public static void main(String[] args) { Listener thread1 = new Listener(); thread1.start(); } } import redis.clients.jedis.Jedis; public class Listener extends Thread { @Override public void run() { Jedis jedisClient = new Jedis("127.0.0.1", 6379); while (true) { String value = jedisClient.lpop("LOCK"); System.err.println(value); if (value == null) { try { System.err.println("================移除完成"); Thread.sleep(1000); System.err.println("================继续监控"); } catch (InterruptedException e) { e.printStackTrace(); } } } }