老项目突然之间客户要用了而且用户量还不少,后端移动端都需要给升级。第一改进的时候做了移动端与后端的服务分流,这次升级为分布式集群模式。分布式集群模式需要解决Session共享问题和数据一致性分布式锁处理。因为历史原因,应用是单体应用并非微服务技术实现。为应对移动端大概20000左右的用户使用量做的如下改造。

目录

服务器端口分配管理

分布式集群session共享管理

Tomcat session共享设置

业务系统Session管理

Nginx集群配置管理

遇到的问题

回顾历史版本改造改进处理


服务器端口分配管理

服务器作用与集群部署的节点说明(实际应用过程中并没有使用到这么多集群节点,规划的多一点备用)。

分布式集群session共享管理

Tomcat session共享设置

做分布式除了Token,一般需要解决Session共享问题。

  • Tomcat的session共享Redis方案可以参考:https://github.com/mzd123/session_manager
  • 国外开源项目: https://github.com/jcoleman/tomcat-redis-session-manager
  • 推荐开源项目支持Tomcat789: https://github.com/ran-jit/tomcat-cluster-redis-session-manager

这一块都是基于tomcat的改造细节就不多说了,实验过程用的是mzd123的。配置说明:

1、修改Tomcat/config/redis-data-cache.properties  =====解决redissession同步问题2、检查Tomcat/lib是否存在commons-logging-1.2.jar、commons-pool2-2.4.2.jar、jedis-2.9.0.jar、tomcat-cluster-redis-session-manager-2.0.4.jar   =====解决redissession同步问题3、设置Tomcat/config/context.xml    =====解决redissession同步问题<!--redis管理session配置--><Valve className="tomcat.request.session.redis.SessionHandlerValve"/><Manager className="tomcat.request.session.redis.SessionManager"/>

节点可以看下Tomcat目录结构:

业务系统Session管理

首先添加一个Session监听监控应用系统Session创建和销毁等操作,下面的代码有其他业务凑合看吧:

SessionListener.java

package com.boonya.listener;import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.log4j.Logger;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.boonya.cache.XHTSystemConfig;
import com.boonya.webservice.util.RedisDistributedLock;
import com.boonya.webservice.util.RedisUtil;
import com.boonya.xht.util.Constants;
import data.common.util.StringUtils;/*** * @function 功能:系统Session管理* @author PJL* @package com.boonya.listener* @filename SessionListener.java* @time 2019年12月19日 下午6:08:03*/
public class SessionListener implements HttpSessionListener,HttpSessionAttributeListener {private static Logger logger = Logger.getLogger(SessionListener.class);/*** 保存当前登录的所有用户*/public static Map<HttpSession, String> loginUser = new ConcurrentHashMap<HttpSession, String>();/*** 记录当前在线用户数量*/public static Long loginCount = 0L;/*** 用这个作为session中的key,userName 为手机号*/public static String SESSION_LOGIN_NAME = "userName";/*** session创建时监听*/@Overridepublic void sessionCreated(HttpSessionEvent arg0) {//logger.info("sessionCreated");}/*** session销毁时*/@Overridepublic void sessionDestroyed(HttpSessionEvent arg0) {if (XHTSystemConfig.clusterModeForTomcat) {try {// 删除共享REDIS SESSION KEYRedisUtil.expire(arg0.getSession().getId(), Constants.MOBILE_TOKEN_KEY_EXPIRED_NOW);// 删除REDIS SESSION KEYString key=Constants.CLUSTER_USER_SESSION_KEY+arg0.getSession().getId();RedisUtil.expire(key, Constants.MOBILE_TOKEN_KEY_EXPIRED_NOW);} catch (Exception e) {e.printStackTrace();}finally{// 更新登录session数量updateLoginUserCount();}} else {try {// 移除用户sessionloginUser.remove(arg0.getSession());} catch (Exception e) {e.printStackTrace();}}}/*** 添加属性时*/@Overridepublic void attributeAdded(HttpSessionBindingEvent arg0) {if (XHTSystemConfig.clusterModeForTomcat) {// 如果添加的属性是用户名, 则加入map中if (arg0.getName().equals(SESSION_LOGIN_NAME)) {try {// REDIS SESSION KEYString key=Constants.CLUSTER_USER_SESSION_KEY+arg0.getSession().getId();// 设置登录sessionRedisUtil.hset(key, arg0.getSession().getId(), arg0.getValue().toString());RedisUtil.expire(key, Constants.MOBILE_TOKEN_KEY_ONE_HOUR);// 设置登录用户名String userName=(String) arg0.getSession().getAttribute(SESSION_LOGIN_NAME);String userNameKey=Constants.CLUSTER_USER_SESSION_USER+arg0.getSession().getId();RedisUtil.hset(userNameKey, userName,userName);RedisUtil.expire(userNameKey, Constants.MOBILE_TOKEN_KEY_ONE_HOUR);// 更新登录session数量updateLoginUserCount();} catch (Exception e) {e.printStackTrace();}}} else {// 如果添加的属性是用户名, 则加入map中if (arg0.getName().equals(SESSION_LOGIN_NAME)) {loginUser.put(arg0.getSession(), arg0.getValue().toString());loginCount++;}}}/*** 移除属性时*/@Overridepublic void attributeRemoved(HttpSessionBindingEvent arg0) {if (XHTSystemConfig.clusterModeForTomcat) {// 如果添加的属性是用户名, 则加入map中if (arg0.getName().equals(SESSION_LOGIN_NAME)) {try {// REDIS SESSION KEYString key=Constants.CLUSTER_USER_SESSION_KEY+arg0.getSession().getId();// 设置登录session立即失效RedisUtil.expire(key, Constants.MOBILE_TOKEN_KEY_EXPIRED_NOW);// 设置登录用户名String userNameKey=Constants.CLUSTER_USER_SESSION_USER+arg0.getSession().getId();RedisUtil.expire(userNameKey, Constants.MOBILE_TOKEN_KEY_EXPIRED_NOW);// 更新登录session数量updateLoginUserCount();} catch (Exception e) {e.printStackTrace();}}} else {// 如果移除的属性是用户名, 则从map中移除if (arg0.getName().equals(SESSION_LOGIN_NAME)) {try {loginUser.remove(arg0.getSession());loginCount--;} catch (Exception e) {}}}}/*** 属性更新时*/@Overridepublic void attributeReplaced(HttpSessionBindingEvent arg0) {if (XHTSystemConfig.clusterModeForTomcat) {if (arg0.getName().equals(SESSION_LOGIN_NAME)) {try {// REDIS SESSION KEYString key=Constants.CLUSTER_USER_SESSION_KEY+arg0.getSession().getId();// 设置登录sessionRedisUtil.hset(key, arg0.getSession().getId(), arg0.getValue().toString());RedisUtil.expire(key, Constants.MOBILE_TOKEN_KEY_ONE_HOUR);// 设置登录用户名String userName=(String) arg0.getSession().getAttribute(SESSION_LOGIN_NAME);String userNameKey=Constants.CLUSTER_USER_SESSION_USER+arg0.getSession().getId();RedisUtil.hset(userNameKey, userName,userName);RedisUtil.expire(userNameKey, Constants.MOBILE_TOKEN_KEY_ONE_HOUR);} catch (Exception e) {e.printStackTrace();}finally{// 更换新登录用户数量updateLoginUserCount();}}} else {if (arg0.getName().equals(SESSION_LOGIN_NAME)) {loginUser.put(arg0.getSession(), arg0.getValue().toString());}}}/*** 判断当前用户是否已经登录* * @param userId* @return*/public static boolean isLogonUser(String userName) {if (XHTSystemConfig.clusterModeForTomcat) {HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String username=RedisUtil.hget(Constants.CLUSTER_USER_SESSION_USER+request.getSession().getId(), userName);if(StringUtils.IsNullOrEmpty(username)){return false;}return true;} else {Set<HttpSession> keys = SessionListener.loginUser.keySet();for (HttpSession key : keys) {if (SessionListener.loginUser.get(key).equals(userName)) {return true;}}return false;}}/*** 判断当前session是否已经登录* * @param hs* @return*/public static boolean isLogonUser(HttpSession hs) {if (XHTSystemConfig.clusterModeForTomcat) {String sessionValue=RedisUtil.hget(Constants.CLUSTER_USER_SESSION_KEY+hs.getId(), hs.getId());if(StringUtils.IsNullOrEmpty(sessionValue)){return false;}return true;} else {Set<HttpSession> keys = SessionListener.loginUser.keySet();for (HttpSession key : keys) {if (key.equals(hs)) {return true;}}return false;}}/*** 获取当前登录用户的数量* * @return*/public static Long getLoginUserCount() {if (XHTSystemConfig.clusterModeForTomcat) {// 获取数量String key=Constants.CLUSTER_USER_SESSION_KEY.substring(0, Constants.CLUSTER_USER_SESSION_KEY.length()-1);String newKey=key+":*";// 模糊查询Set<String> keys=RedisUtil.keys(newKey);Long userNumber=0L;if(null!=keys){userNumber=Long.valueOf( keys.size()+"");}return userNumber;} else {return loginCount;}}/*** 更新用户数量统计* */public static long updateLoginUserCount(){final String requestId=UUID.randomUUID().toString();boolean success=RedisDistributedLock.tryGetDistributedLock(Constants.CLUSTER_APPLICATION_COUNT_LOCK, requestId,Constants.CLUSTER_APPLICATION_COUNT_LOCK_TIME);logger.error("更新USER COUNT分布式锁:"+success);// 获取登录session数量long countSession=getLoginUserCount();while(!success){try {Thread.sleep(100);logger.error("更新USER COUNT分布式锁:休眠1000ms!");} catch (InterruptedException e) {e.printStackTrace();}success=RedisDistributedLock.tryGetDistributedLock(Constants.CLUSTER_APPLICATION_COUNT_LOCK, requestId,Constants.CLUSTER_APPLICATION_COUNT_LOCK_TIME);if(success){countSession=getLoginUserCount();break;}}// 分布式不宜进行加减运算RedisUtil.set(Constants.CLUSTER_USER_SESSION_COUNT, countSession+"");RedisDistributedLock.releaseDistributedLock(Constants.CLUSTER_APPLICATION_COUNT_LOCK, requestId);logger.error("释放更新USER COUNT分布式锁成功!");return countSession;}/*** 清除已经登录用户的缓存* * @param userName*/@SuppressWarnings("rawtypes")public static void removeSession(String userName) {if (XHTSystemConfig.clusterModeForTomcat) {try {HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// REDIS SESSION KEYString key=Constants.CLUSTER_USER_SESSION_KEY+request.getSession().getId();// 设置登录session立即失效RedisUtil.expire(key, Constants.MOBILE_TOKEN_KEY_EXPIRED_NOW);// 更换新登录用户数量updateLoginUserCount();} catch (Exception e) {e.printStackTrace();}} else {Set<HttpSession> keys = SessionListener.loginUser.keySet();// 如果使用老remove方法则要用iterator遍历。Iterator iterator = keys.iterator();while (iterator.hasNext()) {Object key = iterator.next();if (!StringUtils.IsNullOrEmpty(userName)&& userName.equals(SessionListener.loginUser.get(key))) {/** 这行代码是关键。否则报concurrentModificationException **//** 详情原因见:http://www.cnblogs.com/dolphin0520/p/3933551.html **/iterator.remove();loginUser.remove(key);loginCount--;}}}}}

注意:核心关注Session创建和销毁。

web.xml配置监听:

<listener><listener-class>com.boonya.listener.SessionListener</listener-class>
</listener>

Nginx集群配置管理

106前置机内网外服务器Nginx配置(服务和代理在同一台机器),之前有同事建议做每个集群的虚拟机部署,因为只是为了验收收钱不必要做的太复杂故而未予以采纳。

http {include       mime.types;#default_type  application/octet-stream;default_type  text/html;charset utf-8;#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '#                  '$status $body_bytes_sent "$http_referer" '#                  '"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main;sendfile        on;#tcp_nopush     on;#keepalive_timeout  0;keepalive_timeout  65;#nginx服务器与被代理服务连接超时时间,代理超时proxy_connect_timeout 300;#nginx服务器发送数据给被代理服务器超时时间,单位秒,#规定时间内nginx服务器没发送数据,则超时proxy_send_timeout 300;#nginx服务器接收被代理服务器数据超时时间,单位秒,#规定时间内nginx服务器没收到数据,则超时proxy_read_timeout 300;proxy_buffer_size 64k;proxy_buffers   8  128k;proxy_busy_buffers_size 128k;proxy_temp_file_write_size 128k;# 客户端请求头设置client_header_buffer_size 10m;# 客户端请求体过大设置client_max_body_size 128m;#gzip  on;gzip  on;#BS WEB集群配置:服务器列表10upstream WebCluster{server localhost:9001 weight=1;server localhost:9002 weight=1;server localhost:9003 weight=1;server localhost:9004 weight=1;server localhost:9005 weight=1;server localhost:9006 weight=1;server localhost:9007 weight=1;server localhost:9008 weight=1;server localhost:9009 weight=1;server localhost:9010 weight=1;}#APP集群配置:服务器列表3upstream AppCluster{server localhost:9021 weight=1;server localhost:9022 weight=1;server localhost:9023 weight=1;}server {listen       80;server_name  localhost;#charset koi8-r;#access_log  logs/host.access.log  main;# 限请求数配置#limit_req_zone $binary_remote_addr zone=perip:20m rate=100r/s;#limit_req_zone $server_name zone=perserver:20m rate=10000r/s;#移动端服务代理转发配置IP地址请求适配location ^~ /webService/ {proxy_pass  http://AppCluster/webService/;proxy_redirect    off;proxy_set_header  Host $host;proxy_set_header  X-real-ip $remote_addr;proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;#limit_req zone=perip  burst=1000;#limit_req zone=perserver burst=100000;}# 集群统一文件访问路径location ^~ /upload/ {alias  D:/application-images/upload/;}# 移动端APP下载代理location /forestryapp/ {alias D:/forestryapp/;}# 默认访问后台管理系统服务location / {proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";#root   html;#index  home.html index.html server.html;proxy_pass http://WebCluster;}#.....}
#.....
}

遇到的问题

1、连接池关闭问题:Jedis连接池未关闭导致Redis连接池、Tomcat线程资源耗尽,主要是引入了Redis分布式锁做用户Session数量的统计而这部分代码没有关闭jedis连接,实际上没必要加锁,因为可以直接读取节点获得数量,只是为了从redis里面直接看结果而做的辅助处理。

2、共享Session 移除问题:隐约觉得哪里没有做完就匆忙上线了,结果用户的组织机构无法过滤,因为共享session没有移除即使单个Tomcat注销了但是整个Redis管理的共享session并没有被移除掉,只需要在session销毁的地方移除掉共享Session的redis key。

3、集群节点卡壳:没有启动的节点Nginx配置策略没有完善导致轮询过程中卡顿。

回顾历史版本改造改进处理

1、第一次改造:移动端和后端分流。

2、第二次改造:MySQL数据库索引解决查询慢的问题(包括轨迹查询、统计分析等)。

3、第三次改造:从内存方式全面转向Redis读取。

4、第四次改造:Redis主从方式,从节点只读负责系统业务数据查看。

5、第五次改造:组织机构人员树与地图联动分离后地图聚合数据改造,分布式集群支持。

注:由于历史原因组织机构人员树加载要10几秒,上万节点后加上业务耦合到树上计算基本树就没法用了。这次树与地图联动分离页面加载提升到1秒以内,并且得到了客户的认可这是最可贵的,索引查询数据5万左右的可以做到毫秒级分页加载。

Redis解决老项目集群Session共享案例与回顾相关推荐

  1. Nginx+memcached+tomcat配置集群session共享负载均衡

    Nginx+memcached+tomcat配置集群session共享负载均衡 配置环境: windows xp下 jdk1.7.0_10      nginx-1.2.6  (附下载)      m ...

  2. Windows7 + Nginx + Memcached + Tomcat 集群 session 共享

    一,环境说明 操作系统是Windows7家庭版(有点不专业哦,呵呵!),JDK是1.6的版本, Tomcat是apache-tomcat-6.0.35-windows-x86,下载链接:http:// ...

  3. Redis 解决 WebSocket 分布式场景下 Session共享问题

    点击上方"服务端思维",选择"设为星标" 回复"669"获取独家整理的精选资料集 回复"加群"加入全国服务端高端社群「后 ...

  4. spring-session + redis 实现集群 session 共享

    2019独角兽企业重金招聘Python工程师标准>>> 目前市面上实现session共享的方案有很多,其中比较常用的是使用Tomcat.Jetty等web服务器提供的session共 ...

  5. php 集群 session共享,Session共享:php和redis集群如何实现Session共享

    本篇文章给大家带来的内容是关于Session共享:php和redis集群如何实现Session共享,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一.redis 数据库集群安装软件版 ...

  6. MSM搭建(Memcached_Session_Manager)--解决集群session共享

    一.环境 tomcat7三台,nginx(负载均衡), memcached(1.4.0) 需要的jar 二.memcached搭建     需要安装 libevent 三.tomcat配置 在tomc ...

  7. mysql集群session_PHP集群session共享

    集群的概念没有多复杂,其实就是多台电脑为了同一个目标在一起工作.在Web应用中,就是多个服务器提供一个站点的服务. 搭建PHP集群的第一步就是设置负载均衡.首先我们需要三台主机: Nginx负载:19 ...

  8. tomcat集群session共享

    在部署cas系统的时候,遇到个问题,在tomcat集群部署cas系统,不能在一边服务器实现了登陆而在另一侧服务器还是没登录的.所以要实现tomcat集群的session的共享,从而实现高可用. 了解到 ...

  9. Jetty9.2.2集群Session共享

    为什么80%的码农都做不了架构师?>>>    针对Jetty就不过多的介绍了,自行研究去吧! 1.准备环境 MySQL数据库:下载地址:自行百度. jetty-distributi ...

最新文章

  1. iOS asset 中定义颜色,xib中便捷访问
  2. mongodb 简单部署方案及实例
  3. [导入]SQL中的临时表和表变量
  4. css中float详解,CSS浮动属性Float详解?史上最全Float详解
  5. 图像目标分割_3 SegNet + U-Net
  6. 转载 - Linux 磁盘挂载
  7. Flutter 踩坑 在bottomNavigationBar下显示bottomSheet
  8. linux学习笔记(13)终端清屏
  9. CR开发笔记-2基础飞机的搭建以及测试代码
  10. matlab 行 读取文件 跳过_Matlab调试:跳过下一行而不执行
  11. Qt For Android | QT安卓开发环境搭建
  12. gbox推荐源_分享一批自己用的软件源 gbox软件源
  13. 联想台式机计算机接口,我的电脑显卡是什么样的接口?
  14. malloc、calloc、realloc函数
  15. python的rs232通信_RS-232与Python的通信返回Gibberish
  16. WEB前端面试2014阿里旺旺
  17. connect的中文意思是什么_connect是什么意思中文翻译
  18. powershell提取html字段,有选择地格式化PowerShell管道中的数据并以HTML格式输出的技术...
  19. 计算机常用的英语简写,计算机常用的英文名称和缩写
  20. 34. 注入篇——Cookie注入

热门文章

  1. Cloudflare免费更换节点,加速你的网站
  2. [转载]playframework的视频短片
  3. 用Photoshop给照片换美丽天空
  4. 面试题: -- 11.02.24
  5. OpenGLES glUniform1i用法
  6. Golden Gate简介
  7. 【Go】FLV文件解析(二)
  8. 微软借“云”掀估值高涨浪潮,百度借智能云启动千亿美金估值航母?
  9. vue3 :deep() :slotted() :global() css动态绑定变量
  10. oracle列转行 多个字段_oracle 多列 列转行