最近在做一个手机App项目. 需求如下.
1: 查看最近登录用户列表以及其当前状态(在线/离线/忙碌/密聊)
2: 根据用户状态以及语音时长排序, 显示用户以及该用户发表的最后一个帖子列表.(固定显示前50名)
如: 张三+张三最后帖子--> 李四+李四最后帖子..........
3: 显示所有用户信息以及用户发表帖子. 按时间顺序排列.
1和3就不说了. 解决了2, 其余的都是渣渣. 先讲讲我的实现过程.
阶段1:
因为用户状态是实时变化的, 语音时长也是实时变化. 因此在根据这种方式进行排序. 每次查询都会有不同的结果. 存放数据库当然扛不住. 涉及到数据库的频繁读写. 因此考虑到将用户信息以及状态存放到 Redis中.
根据业务需求, 认证空闲>认证忙碌>认证挂机>普通在线>认证离线> 普通离线.
解决思路: 定义各状态的基础值, 再加上语音时长作为一个集合的score值. 根据该score排序. (灵感来源于做炸金花时各种牌力值大小比较.)
public static final BigDecimal RENZHENG_KONGXIAN = new BigDecimal("900000000"); // 认证空闲
public static final BigDecimal RENZHENG_MANGLU = new BigDecimal("800000000"); // 认证忙碌
public static final BigDecimal RENZHENG_GUAJI = new BigDecimal("700000000"); // 认证挂机
public static final BigDecimal PUTONG_ZAIXIAN = new BigDecimal("600000000"); // 普通在线
public static final BigDecimal RENZHENG_LIXIAN = new BigDecimal("500000000"); // 认证离线
public static final BigDecimal PUTONG_LIXIAN = new BigDecimal("400000000"); // 普通离线
/**
* @Notes : 更新用户排序
* @Author: songzj
* @Date : 2015年5月14日 下午7:53:24
* @param uin
* @param state
*/
private static void updateUserSort(int uin, byte state) {
JedisCluster jc = JedisPoolUtil.getJedisCluster();
// 获取该用户帖子总数.
String topics = jc.hget(LOGON_USER_INFO + uin, "topic");
if (topics != null && Integer.parseInt(topics) > 0) {
String time = jc.hget(LOGON_USER_INFO + uin, "times"); // 获取语音聊天总时长
time = Utils.isBlank(time) ? "0" : time;
BigDecimal times = new BigDecimal(time);
String vip = jc.hget(LOGON_USER_INFO + uin, "vip");// 认证用户
vip = Utils.isBlank(vip) ? "0" : vip;
if ("0".equals(vip)) {// 非认证
switch (state) {
case 0:
case 1:
case 2:
case 3:
times = PUTONG_ZAIXIAN.add(times);
break;
case 4:
times = PUTONG_LIXIAN.add(times);
break;
}
} else {// 认证
switch (state) {
case 0:
times = RENZHENG_KONGXIAN.add(times);
break;
case 1:
times = RENZHENG_MANGLU.add(times);
break;
case 2:
times = RENZHENG_GUAJI.add(times);
break;
case 4:
times = RENZHENG_LIXIAN.add(times);
break;
}
}
// 更新用户状态列表score
jc.zadd(USER_SORT_LIST, times.doubleValue(), "" + uin);
// 更新标签内排序.
String tagId = jc.hget(LOGON_USER_INFO + uin, "tagId");
if (Utils.isNotBlank(tagId)) {
jc.zadd(USER_TAG_LIST + tagId, times.doubleValue(), uin + "");
}
} else {
jc.zadd(USER_SORT_LIST, 0, "" + uin);
}
}
好了, 根据用户状态实时排序已经OK.
但是取用户列表和帖子列表的时候有点坑. 由于使用的是集群, 不同的用户数据, 帖子数据存放在不同的机器上. 且使用Jedis时不能使用管道. (如有大神有好的方案,请赐教). 需要一个个用户一条条帖子去读取. 中间网络传输占了很多的资源和时间. 因此效率低下.
简单用ab测了一下, 单台tomcat. 10000次请求. 100个并发才 300~500 tps. 严重慢.
再搭两个tomcat. 前面用 nginx 做代理. 使用多个tomcat来扛.
nginx配置如下.
upstream mfs_server{ server 1.251.192.37; server 1.251.192.47:8080; server 1.251.192.47:8085; keepalive 128; }
location /mfs{
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://mfs_server;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
重启nginx. 同样的条件ab跑一次, 性能提升了3被. 但是传输率还不够. 网卡没跑满最大10M. 对tomcat和nginx都进行了优化. 提升量还不是很大.
从业务角度考虑. 用户的基本信息和帖子信息是没必要实时更新和显示的. 15s~30s的延时是用户可以容忍的.
因此考虑到使用nginx做缓存. 这样过来请求直接nginx处理.
java代码处理如下. 返回页面时设置过期时间. 让nginx在这段时间内不要再来骚扰tomcat.
long cur = System.currentTimeMillis() + (8 * 60 * 60 * 1000);
response.setHeader("ETag", Long.valueOf(cur).toString());
response.setHeader("Cache-Control", "public,max-age=60");
response.setHeader("Cache-Control", "max-age=60");
long adddaysM = 60 * 1000;
response.addDateHeader("Last-Modified", cur);
response.addDateHeader("Expires", cur + adddaysM);
String requestUrl = request.getRequestURI();
nginx配置如下.
client_body_buffer_size 512k;
proxy_connect_timeout 60;
proxy_read_timeout 60;
proxy_send_timeout 60;
proxy_buffer_size 16k;
proxy_buffers 8 128k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
gzip on;
#nginx开启缓存. 内存中开启一块10m的空间, 取名Z.
proxy_cache_path /home/songzj/cache levels=1:2 keys_zone=Z:10m inactive=1 max_size=1g;
location /mfs/topic/topicList{
proxy_pass http://mfs_server; //去请求后台tomcat. 默认round_robin
proxy_set_header X-Forwarded-For $remote_addr;
proxy_cache Z; //去Z的缓存里取
proxy_cache_min_uses 1;
proxy_cache_valid 200 302 1m;
proxy_cache_valid 404 1m;
}
location /mfs/topic/topicList{
proxy_pass http://mfs_server;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_cache Z;
proxy_cache_min_uses 1;
proxy_cache_valid 200 302 1m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale updating invalid_header error timeout http_500;
}