精华内容
下载资源
问答
  • 在有的业务中,当更改状态时,可能需要大量的轮询来实现,用websocket能够很好的实现,但是因为工作中很多都是采取服务器集群来实现的,所以对集群情况下的websocket进行了学习,在围观大佬之后,进行了改造,使之...

    在有的业务中,当更改状态时,可能需要大量的轮询来实现,用websocket能够很好的实现,但是因为工作中很多都是采取服务器集群来实现的,所以对集群情况下的websocket进行了学习,在围观大佬之后,进行了改造,使之贴合我们公司架构,springmvc。

    github地址:https://github.com/onthewayw/springmvc_websocket_mq.git

    1:pom.xml

    <properties>
        <spring-version>4.3.18.RELEASE</spring-version>
        <servlet-version>3.1.0</servlet-version>
        <log4j-version>1.2.17</log4j-version>
        <slf4j-log4j12-version>1.7.9</slf4j-log4j12-version>
        <slf4j-api-version>1.7.9</slf4j-api-version>
        <junit-version>4.12</junit-version>
    </properties>
    
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.3</version>
            <scope>runtime</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.8.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j-log4j12-version}</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j-version}</version>
        </dependency>
    
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j-api-version}</version>
        </dependency>
    
        <!-- servlet
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet-version}</version>
            <scope>provided</scope>
        </dependency>-->
    
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit-version}</version>
        </dependency>
    
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.7</version>
        </dependency>
        <!-- 添加MQ依赖 -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>1.4.5.RELEASE</version>
        </dependency>
        <!--这里需要引入jackson库,否则jsonMessageConverter实例化就会报错,如果使用Gson需要实现jsonMessageConverter的父类方法自己手动转一次,然后jsonMessageConverter的配置换成自己实现的类-->
        <!--Jackson 核心库-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.8.9</version>
        </dependency>
        <!--Jackson 序列化库-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.9</version>
        </dependency>
        <!--Jackson 注解支持-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.8.9</version>
        </dependency>
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    2.redis.xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:redis="http://www.springframework.org/schema/redis"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd
    ">
        <context:component-scan base-package="com.wang.*" />
        <context:property-placeholder location="classpath:spring-rabbitMQ.properties" ignore-unresolvable="true"/>
        <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
            <property name="maxIdle" value="${redis.maxIdle}" />
            <property name="minIdle" value="${redis.minIdle}" />
            <property name="maxTotal" value="${redis.maxTotal}" />
            <property name="testOnBorrow" value="true" />
        </bean>
    
        <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
              p:host-name="${redis.ip}" p:port="${redis.port}" p:password="${redis.password}"
              p:pool-config-ref="jedisPoolConfig" p:usePool="true"/>
        <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
        <!-- Redis连接-->
        <!--<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
              p:host-name="192.168.19.129" p:port="6379" p:password="123456">
            <constructor-arg ref="jedisPoolConfig" />
        </bean>-->
        <!-- 缓存序列化方式 -->
        <bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        <bean id="valueSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
            <property name="connectionFactory" ref="jedisConnectionFactory" />
            <property name="keySerializer" ref="keySerializer" />
            <property name="valueSerializer" ref="valueSerializer" />
            <property name="hashKeySerializer" ref="keySerializer" />
            <property name="hashValueSerializer" ref="valueSerializer" />
        </bean>
        <!--<bean id="redisListenerContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
            <property name="connectionFactory" ref="jedisConnectionFactory"/>
        </bean>-->
        <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/>
        <!--序列化-->
        <bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        <!-- 配置监听器,redis在订阅消息时,会根据 redis:listener标签指定的方法名和通道(topic)调用不同的方法-->
        <bean id="listener" class="com.wang.listener.SubscribeListener" />
    
        <redis:listener-container connection-factory="jedisConnectionFactory">
        <!--   the method attribute can be skipped as the default method name is
                "handleMessage"
            topic代表监听的通道,是一个正规匹配 -->
    
        <redis:listener ref="listener" serializer="jdkSerializer" method="handleMessage" topic="im-*" />
        </redis:listener-container>
    
    </beans>
    

    3 订阅监听类,对消息进行监听

    public class SubscribeListener implements MessageListener {
    
        private final Logger logger = LoggerFactory.getLogger(SubscribeListener.class);
    
        private StringRedisTemplate redisTemplate;
    
        private Session session;
    
        public Logger getLogger() {
            return logger;
        }
    
        public StringRedisTemplate getRedisTemplate() {
            return redisTemplate;
        }
    
        public void setRedisTemplate(StringRedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        public Session getSession() {
            return session;
        }
    
        public void setSession(Session session) {
            this.session = session;
        }
    
        @Override
        public void onMessage(Message message, byte[] pattern) {
            String msg = new String(message.getBody());
            logger.info(new String(pattern) + "主题发布:" + msg);
            if(null!=session){
                try {
                    session.getBasicRemote().sendText(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    4 websocket端点

    @ServerEndpoint(value = "/im_webSocket/{topic}/{username}")
    public class WebsocketEndpoint {
        /**
         * 因为@ServerEndpoint不支持注入,所以使用SpringUtils获取IOC实例
         */
        private StringRedisTemplate redisTemplate = SpringUtils.getBean(StringRedisTemplate.class);
    
        private RedisMessageListenerContainer redisMessageListenerContainer = SpringUtils.getBean(RedisMessageListenerContainer.class);
    
        //存放该服务器该ws的所有连接。用处:比如向所有连接该ws的用户发送通知消息。
        private static CopyOnWriteArraySet<WebsocketEndpoint> sessions = new CopyOnWriteArraySet<>();
    
        private Session session;
    
        @OnOpen
        public void onOpen(Session session, @PathParam("topic")String topic){
            System.out.println("java websocket:打开连接:topic------"+topic);
            this.session = session;
            sessions.add(this);
            SubscribeListener subscribeListener = new SubscribeListener();
            subscribeListener.setSession(session);
            subscribeListener.setRedisTemplate(redisTemplate);
            //设置订阅topic
            try{
                redisMessageListenerContainer.addMessageListener(subscribeListener,new ChannelTopic(topic));
            }catch (Exception e){
                e.printStackTrace();
            }
    
        }
        @OnMessage
        public void onMessage(Session session, String message,@PathParam("topic")String topic,@PathParam("username") String username){
            System.out.println("websocket 收到消息:"+message);
            PulishService pulishService = SpringUtils.getBean(PulishService.class);
            pulishService.publish(topic, message);
        }
        @OnClose
        public void onClose(Session session){
           System.out.println("java websocket:关闭连接");
            sessions.remove(this);
        }
        @OnError
        public void onError(Session session,Throwable error){
            System.out.println("java websocket 出现错误");
        }
    
        public Session getSession() {
            return session;
        }
    
        public void setSession(Session session) {
            this.session = session;
        }
    }
    

    5.service

    @Component
    public class PulishService {
        @Autowired(required = false)
        private StringRedisTemplate redisTemplate;
        public void publish(String channel, Object message){
            //该方法封装的 connection.publish(rawChannel, rawMessage);
            redisTemplate.convertAndSend(channel,message);
        }
    }
    

    6.controller

    @Controller
    @RequestMapping("webSocket")
    public class WebsocketController {
    
        @RequestMapping("/index/{topic}/{username}")
        public ModelAndView index(@PathVariable("topic") String topic, @PathVariable("username") String username) {
            ModelAndView mv = new ModelAndView("/websocket_1");
            mv.addObject("topic", topic);
            mv.addObject("username", username);
            return mv;
        }
    }

    7.客户端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8"></meta>
        <title>websocket+redis集群</title>
    </head>
    <input type="hidden" id="port" value='${port }'>
    <input type="hidden" id="topic" value='${topic }'>
    <input type="hidden" id="username" value='${username }'>
    <body>
    ${topic} 频道 聊天中。。。<br/>
    <input id="input_id" type="text" /><button onclick="sendMessage()">发送</button>    <button onclick="closeWebsocket()">关闭</button>
    <div id="message_id"></div>
    </body>
    <script>
        var topic = document.getElementById("topic").value;
        var username = document.getElementById("username").value;
        var websocket = new WebSocket('ws://127.0.0.1:8081/im_webSocket/'+topic+'/'+username);
        console.log(websocket);
        websocket.onopen = function(event){
            setMessage("打开连接");
        }
    
        websocket.onclose = function(event){
            setMessage("关闭连接");
        }
    
        websocket.onmessage = function(event){
            setMessage(event.data);
        }
    
        websocket.onerror = function(event){
            setMessage("连接异常");
        }
    
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function(){
            closeWebsocket();
        }
    
        //关闭websocket
        function closeWebsocket(){
            //3代表已经关闭
            if(3!=websocket.readyState){
                websocket.close();
            }else{
                alert("websocket之前已经关闭");
            }
        }
    
        //将消息显示在网页上
        function setMessage(message){
            document.getElementById('message_id').innerHTML += message + '<br/>';
        }
    
        //发送消息
        function sendMessage(){
            //1代表正在连接
            if(1==websocket.readyState){
                var message = document.getElementById('input_id').value;
                //setMessage(message);
                websocket.send(message);
            }else{
                alert("websocket未连接");
            }
            document.getElementById('input_id').value="";
            document.getElementById('input_id').focus();
        }
    </script>
    </html>
    

    在写的时候出现了如下错误:

    org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.data.redis.listener.RedisMessageListenerContainer' available: expected single matching bean but found 2: redisListenerContainer,org.springframework.data.redis.listener.RedisMessageListenerContainer#0
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041)

    那是因为在这里多注入了一次。

    因为@ServerEndpoint注解之下并不能用@Autowired进行注入,所以用springUtil工具类进行注入

    springUtil工具类

    package com.wang.util;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public final class SpringUtils implements BeanFactoryPostProcessor {
    
        private static ConfigurableListableBeanFactory beanFactory; // Spring应用上下文环境
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            SpringUtils.beanFactory = beanFactory;
        }
    
        public static ConfigurableListableBeanFactory getBeanFactory() {
            return beanFactory;
        }
    
        /**
         * 获取对象
         *
         * @param name
         * @return Object 一个以所给名字注册的bean的实例
         * @throws org.springframework.beans.BeansException
         *
         */
        @SuppressWarnings("unchecked")
        public static <T> T getBean(String name) throws BeansException {
            return (T) getBeanFactory().getBean(name);
        }
    
        /**
         * 获取类型为requiredType的对象
         *
         * @param clz
         * @return
         * @throws org.springframework.beans.BeansException
         *
         */
        public static <T> T getBean(Class<T> clz) throws BeansException {
            T result = (T) getBeanFactory().getBean(clz);
            return result;
        }
    
        /**
         * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
         *
         * @param name
         * @return boolean
         */
        public static boolean containsBean(String name) {
            return getBeanFactory().containsBean(name);
        }
    
        /**
         * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
         *
         * @param name
         * @return boolean
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
            return getBeanFactory().isSingleton(name);
        }
    
        /**
         * @param name
         * @return Class 注册对象的类型
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
            return getBeanFactory().getType(name);
        }
    
        /**
         * 如果给定的bean名字在bean定义中有别名,则返回这些别名
         *
         * @param name
         * @return
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
            return getBeanFactory().getAliases(name);
        }
    
    }

    或者还有一种方法,如下:

    在其中加入如上代码,就可以进行注入。

    Redis发布订阅与ActiveMQ的比较

    (1)ActiveMQ支持多种消息协议,包括AMQP,MQTT,Stomp等,并且支持JMS规范,但Redis没有提供对这些协议的支持; 
    (2)ActiveMQ提供持久化功能,但Redis无法对消息持久化存储,一旦消息被发送,如果没有订阅者接收,那么消息就会丢失; 
    (3)ActiveMQ提供了消息传输保障,当客户端连接超时或事务回滚等情况发生时,消息会被重新发送给客户端,Redis没有提供消息传输保障。 
    总之,ActiveMQ所提供的功能远比Redis发布订阅要复杂,毕竟Redis不是专门做发布订阅的,但是如果系统中已经有了Redis,并且需要基本的发布订阅功能,就没有必要再安装ActiveMQ了,因为可能ActiveMQ提供的功能大部分都用不到,而Redis的发布订阅机制就能满足需求。

     

    等有时间测试mq集成的例子。希望不足之处多多指正

    展开全文
  • 服务器集群

    2021-03-22 10:34:40
    搭建服务器集群集群服务器集群,是通过多个服务器共同处理负荷,实现负载均衡分担压力的一种措施.可是随之而来也有一些问题:根据用户的反向代理的调用.用户不清楚自己到底访问的是哪台服务器.那么应该如何测试负载均衡...

    搭建服务器集群

    集群

    服务器集群,是通过多个服务器共同处理负荷,实现负载均衡分担压力的一种措施.可是随之而来也有一些问题:根据用户的反向代理的调用.用户不清楚自己到底访问的是哪台服务器.那么应该如何测试负载均衡呢???

    我们可以通过1个url请求获取访问服务器端口号即可.

    现在项目中application.yml文件中修改端口为8081,再通过@value动态赋值为属性,代码如下:

    @RestController
    public class PortController {
        /**
         * 通过Spring容器动态获取YML配置文件中的端口即可
         */
        @Value("${server.port}")
        private int port;
    
        @RequestMapping("/getPort")
        public String getPort(){
    
            return "当前访问的端口号为:"+port;
        }
    }

    这样当我们访问项目的getPort映射时就会显示对应服务器的端口号,可以依据此端口号来进行测试.

    搭建tomcat服务器集群

    我们将项目系统打成3个war包程序. 端口号分别为8081/8082/8083,之后通过java命令(java -jar 808X.war)启动3个cmd窗口也就是3台服务器.

    项目打包:
    先修改端口号之后,将maven进行clean/install打包操作.

    通过java -jar 808X.war将项目发布后然后依次在浏览器进行访问测试.

    Nginx集群配置

    集群配置

    我们现在需要通过manage.com的方式访问服务器时,要求通过反向代理的方式实现.要求配置nginx中集群.

    #配置后台管理系统
        server {
            listen 80;
            server_name manage.jt.com;
    
            location / {
                #root 代表文件目录
                #index 代表默认的访问页面
                #proxy_pass 代表发起url请求
                proxy_pass http://jtW;
            }
        }
    
        #配置集群的关键字   通过集群配置tomcat服务器即可
        #默认: 1.轮询的机制
        upstream jtW {
            server 127.0.0.1:8081;
            server 127.0.0.1:8082;
            server 127.0.0.1:8083;
        }

    负载均衡策略说明

    1. 轮询策略:

    根据配置文件的顺序,之后依次访问服务器. 该策略也是默认的机制.

    #默认: 1.轮询的机制
        upstream jtW {
            server 127.0.0.1:8081;
            server 127.0.0.1:8082;
            server 127.0.0.1:8083;
        }
    
    1. 权重策略--weight:

    公司采购服务器都是有时间间隔的. 但是由于服务器新旧不同,硬件版本不同,导致服务器处理能力不同.
    如果上述的问题不做处理,依然采用轮询的机制,则会出现严重的负载不均衡的现象.
    所以需要通过权重的方式平衡压力,weight值越大.

    #配置集群的关键字   通过集群配置tomcat服务器即可
        #默认: 1.轮询的机制  2.权重策略 
        upstream jtW {
            server 127.0.0.1:8081 weight=6;
            server 127.0.0.1:8082 weight=3;
            server 127.0.0.1:8083 weight=1;
        }
    1. IPHASH策略--ip_hash:

    当某些业务需要用户特定的访问固定的服务器时,就要选用iphash机制.

    #默认: 1.轮询的机制  2.权重策略  3.IPHASH
        upstream jtW {
            ip_hash;
            server 127.0.0.1:8081 weight=6;
            server 127.0.0.1:8082 weight=3;
            server 127.0.0.1:8083 weight=1;
        }

    可以了解一下iphash是如何选择服务器的:
    image
    会通过ip地址的hash值对服务器数量进行取模,然后只访问取模为零的服务器,只有该服务器宕机后才会再访问其他的.

    负载均衡补充

    1. down属性:

    如果tomcat服务器发生了宕机的现象,则通过配置文件标识down的属性,则nginx将不会再次访问故障机.
    server 127.0.0.1:8081 down;

    1. backup属性:

    通常情况下 都会部署一些备用机防止由于主机宕机,剩余的机器不能实现高负责从而导致整个服务宕机的问题.
    如果设置了备用机,则正常情况下用户不会访问.但是当主机宕机或者主机遇忙时才会访问.
    server 127.0.0.1:8083 backup;

    1. tomcat高可用配置:

    1).max_fails=1 配置nginx访问服务器的最大的失败次数.
    2).fail_timeout=60s; 理解为一个时间周期. 如果发现服务器宕机,则在60秒内不会再次访问故障机.
    server 127.0.0.1:8081 max_fails=1 fail_timeout=60s;

    展开全文
  • 分布式SESSION的整个一个原理,其实集群有集群的好处,不集群有...服务器集群之后,会出现哪些问题,分布式解决方案,服务器一旦集群以后,会出现哪些问题,这些问题其实都是属于分布式 解决方案里面的,第一个是分布式sess...
    分布式SESSION的整个一个原理,其实集群有集群的好处,不集群有不集群的好处,session原理讲了一下之后,
    
    这节课我们谈到分布式session,谈到分布式session的时候,就是要谈到session服务器集群的时候,会发生哪些问题,
    
    服务器集群之后,会出现哪些问题,分布式解决方案,服务器一旦集群以后,会出现哪些问题,这些问题其实都是属于分布式
    
    解决方案里面的,第一个是分布式session的问题,为什么叫做分布式session的问题,因为session它是存放在服务器上面的,
    
    这个时候一旦服务器集群的时候,sessionId可能对应找不到session
    
    
    服务集群会产生那些问题
    如果服务器产生了集群后,因为session是存放在服务器上,客户端会使用同一个Sessionid在多个不同的服务器
    上获取对应的Session,从而会导致Session不一致问题。
    

    那么第二个问题是什么问题呢,分布式调度平台,这是一个非常重要的问题,尤其一旦你们服务器集群的时候,
    
    肯定是会产生问题的,在这个地方我给大家画个图,最基本的我们这边叫做客户端,既然是客户端的话,我之前讲过的,
    
    中间是会走一个nginx的服务器,反向代理,那么既然要走nginx反向代理的时候,那么在nginx反向代理的背后,
    
    是不是有非常多的nginx集群,集群的目的是为了什么,是为了解决高并发,当你有台服务器宕机之后,比如这边我举个例子,
    
    这边我们叫做TOMCAT01,把它在copy几个,TOMCAT02,TOMCAT03,TOMCAT04,这个时候把它pass掉,这个地方我们这样去看,
    
    配置一些负载均衡的算法,轮询还有随机,我又不多说了,还有IP绑定,常用的就这三种,假设我们的nginx使用轮询机制的
    
    时候,它是会访问到每一台服务器上面去,如果客户端一旦你做集群的时候,这个时候是什么样的过程我讲一下,一旦你在
    
    这边做这么多服务器集群的时候,不一定有分布式session的问题,还有哪些问题呢,第一个是分布式session的问题,
    
    相当于在这个时候,客户端向nginx请求的时候,我的session是存放在服务器上面去的,他发来第一个请求,第一个请求通过
    
    nginx算法到tomcat01,是不是tomcat01这么一台服务器上面去了,他既然分到TOMCAT01服务器上去以后,这个时候就会
    
    返回一个sessionId,给客户端,返回sessionId给客户端的时候,第二次它会轮询到TOMCAT02上面来,这个时候客户端会拿
    
    sessionId去TOMCAT02找的时候,因为我的session是存放在TOMCAT01上面,当你第二次再轮询的时候,轮询到TOMCAT02的时候,
    
    在我的TOMCAT02上面找就根本找不到了,就会产生什么问题呢,就会产生SESSION不同步的问题,是不是这样的,这个你们理解了没有,
    
    你们再去看一下,分布式任务调度平台,你们现在可能没有学,如果服务器集群之后,如何保证定时Job的唯一性,因为你要知道,
    
    我给你们举个例子,如果一旦我的服务器做了集群之后,就是TOMCAT01里面有我的定时JOB代码,因为你这个时候要把你的war包
    
    在TOMCAT01,TOMCAT02,TOMCAT03,TOMCAT04,这四台服务器上都会导入相同war包,一旦你是相同war包的时候,是不是会导致你
    
    四台服务器上的JOB同时执行起来,是不是这样的,想一想,是不是这样的,就是你服务器集群的时候,就是把war包打包到不同的
    
    服务器上面去,会在四台服务器上同时运行,是不是没有唯一了,是不是这样重复了,这是我们后面会学到的,服务器集群之后
    
    会导致定时JOB的唯一性,其实用专业术语表达是什么呢,叫做幂等性,你们后面会学到的,这是第二个问题

    第三个就是分布式锁的解决方案,分布式锁的解决方案,在这边我给大家讲一下,分布式锁很多听说过的,
    
    上一节课我们已经把zookeeper讲完了,分布式锁讲了三四次,理解还是比较深的,那么我给你讲一下,有的人觉得分布式锁
    
    使用redis性能确实不是很好,但是redis里面有一个非常好的框架,其实他还是比较好的,首先分布式素偶有哪些解决方案呢,
    
    基于zookeeper,zookeeper实现分布式锁相对而言是比较简单的,使用临时节点,加上事件通知,还有一个是基于数据库的,
    
    基于数据库完全没有必要说出来,因为别人都懂,还有一种版本是基于redis,基于redis setnx,这种方式它是有缺点的
    
    不推荐的,代码复杂,他很容易产生死锁现象的,在分布式中,SpringCloud里面,对redisson,他实现分布式锁,确实比较不错,
    
    完全可以和zookeeper进行匹配了,redis setnx这种方法不靠谱,很容易产生死锁现象,对redisson这种方式也很简单,
    
    和zookeeper一样的,它是redis中的,提供了很多关于分布式的解决方案,比如分布式锁,这是我们后期会重点讲的,
    
    还有分布式日志收集问题,你们自己想一下,比如我举个例子,这个时候我们客户端向nginx发起请求之后,比如正常情况下打印日志的
    
    时候,他可能通过轮询算法的时候,他这个日志就会打印到TOMCAT01上去的,那么我讲一下,你们可能不知道,我在打印的时候发生
    
    什么问题呢,我们的服务器他有20台节点,20多台节点,就是服务器集群有20几台节点,你们知道我们当时查询日志有多痛苦吗,
    
    怎么查的吗,相当于我们举个例子,分配5个人,一个人查四台,真的是这样的,因为没有用到分布式日志系统,就很麻烦,
    
    一般我们会用到分布式日志收集系统的,你们知不知道分布式日志收集系统用到什么呢,叫elk,也是在我们这边会讲的,
    
    而且大型的电商会有自己内部的分布式日志收集系统,其实原理都是一样的,有的用KAFKA做收集日志,也有的,KAFKA,
    
    这是第四个点

    第五个点是什么,就是你服务器一旦集群之后,可能会出现哪些问题,想一想,分布式事务问题,你们想想分布式事务和集群
    
    有关系吗,分布式事务和集群有没有关系,你们想想,在这边我说一下,分布式事务记住一点,和集群是没有关系的,
    
    是和RPC远程通讯的时候,服务和服务之间实现事务原理,你们很多人把分布式事务理解成很难,其实我可以在这里告诉大家,
    
    分布式事务真的没有你们想的那么难,我自己对分布式事务理解的非常非常透彻,如何纯手写一个分布式事务解决框架,
    
    因为如果你们自己能够写出来的情况下,模仿lcn,因为我看过源码的,因为写的话才能够理解的深刻些,我看过一些人写过的
    
    分布式方案,他们讲的不是很详细,尤其底层这块,他们只讲应用层这块,用什么atomic,jdk,这些都会用,更重要的还是在一个
    
    底层的实现,比较喜欢讲写一个框架出来,其实分布式方案里面还有很多,后面会学到的,叫做一个分布式的配置中心,
    
    你们要了解一下,分布式的配置中心,分布式也是和集群没有关系的,它是属于分布式解决方案,但是他不属于集群关系的,
    
    这个你们可能现在不懂没关系,在这里我大致的讲一下分布式解决方案,我在这边就大体说一下,分布式锁有点偏向于全局ID,
    
    你在做项目的时候遇到哪些问题,那么我在这边讲一下,当问到这个东西以后,我之前在项目中报了一个错,报了一个空指针,
    
    我想知道空指针吗,我在项目中遇到很多问题,因为我们的项目是分布式的,一旦我们的项目做了集群之后,他可能会遇到很多
    
    问题,因为session它是存在服务器里面的,可能使用sessionId去服务器找Session的时候找不到,还有定时JOB

     

    展开全文
  • 集群方案解决方案,集群技术是实现系统高可用性的重要手段,本节将讨论什么是服务器集群、如何建立服务器集群及相关问题
  • 服务器集群服务器集群就是指将很多服务器集中起来一起进行同一种服务,在客户端看来就像是只有一个服务器。集群可以利用多个计算机进行并行计算,从而获得很高的计算速度,也可以用多个计算机做备份,,从而使得...

    一,集群和分布式的区别

    在进入今天的正题之前,对服务器集群和分布式服务器这两个概念进行简要说明。

    服务器集群:服务器集群就是指将很多服务器集中起来一起进行同一种服务,在客户端看来就像是只有一个服务器。集群可以利用多个计算机进行并行计算,从而获得很高的计算速度,也可以用多个计算机做备份,,从而使得任何一个机器坏了整个系统还是能正常运行。

    根据上述的信息简单来说:服务器集中对外提供同一种服务,解决了大量用户访问同一服务存在的高并发问题;多台服务器备份,解决了服务器的高可用问题;不至于说一台服务器宕机, 这个服务就瘫痪了!

    上文中提到的多台服务器备份,备份的不就是会话信息session吗?也就是今天我们要说的服务器集群环境下session的共享问题。

    分布式服务器:随着B/S架构系统的发展,开发一个互联网项目的业务需求也越来越多。如果把这些业务模块都放在一台服务器上来提供服务,这肯定是不现实的,毕竟一台服务器的性能各方面都是有限的。企业普遍会把这些业务模块分别放到不同的服务上来提供服务,来满足大量用户访问不同服务时存在的高并发问题

    简单来说服务器集群和分布式服务器的区别:
    分布式:项目的不同业务模块(例如:用户模块,购物车模块,支付模块),部署在不同的服务器上
    集群:同一个业务(例如:用户模块),部署在多个服务器上

    二,会话技术Cookie,Session

    估计你们会在想:这个标题党,真正的问题不解决,怎么又扯出个会话技术。难道这就是传说中的程序员修复Bug,真正的问题没有解决,又冒出了另一个问题。

    哈哈,不扯皮了。这里主要是为了说明Cookie,Session的联系和区别。

    1.什么是会话?

    用户开一个浏览器访问页面,访问了很多网站,直到用户关闭浏览器,这整个过程我们称作一次会话。

    2.会话过程中的信息存储问题

    HTTP是无状态协议:每次都是单独连接,不能保存用户的信息,通俗来说,就是它区分不了到底是哪一个用户在访问网络资源。而Cookie和Session就很好地解决了这个问题,

    Cookie:
    Cookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问web资源时,浏览器就会带着用户的信息过去。这样,web资源处理的就是用户各自的数据了。

    注意:Cookie是存放在浏览器端,分为会话Cookie和持久性Cookie,默认情况下,Cookie是会话级别的,关闭浏览器就会消失。可以通过设置Cookie的有效时间来实现持久性Cookie存储

    Session:
    虽然Cookie可以存放用户信息,但cookie存储在浏览器端是以明文方式存放,因此安全性较低。而Session是存放在服务器端的,我们把用户的信息保存在服务器端Session中相对安全,然后把服务器创建的Session对象的Id存在中Cookie中,这样用户在访问服务器资源时,Cookie就会带着SessionId过来,服务器端就会根据这个SessionId来找到对应的Session,从而找到存储在Session中的用户信息。

    注意:在访问同一个服务器下的Web资源时,Session只会被创建一次,Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session

    三,服务器集群环境下session的共享问题

    1.反向代理服务器Nginx

    Nginx多在高并发情况下需要使用。其原理就是将用户请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。

    在这里插入图片描述

    在这里插入图片描述

    正向代理:用户想在某个指定的服务器上获取资源
    反向代理:用户只要访问Nginx就可以获取到资源,而不用去关心具体访问的是哪个服务器

    Nginx的使用

    Nginx的安装过程跳过,主要演示Nginx的一些配置。

    打开Nginx的nginx.conf文件,配置如下

    在这里插入图片描述

    #下面这个配置需要自己添加
    	upstream xiaogui_server{
    		server localhost:8080;
    		server 127.0.0.1:8888;
    	}
    
    #修改配置文件,添加  proxy_pass http://xiaogui_server;
     location / {
                root   html;
    			proxy_pass http://xiaogui_server;
                index  index.html index.htm;
            }
    

    在服务器tomcat1下的测试页面:

    <%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
        
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>服务器1111111111111</title>
    <style type="text/css">
    </style>
    </head>
    <body>
    		<h1>服务器1的页面</h1>
    		
    		服务器1创建Session的Id:<%=session.getId() %>
    		
    		<%session.setAttribute("username", "xiaogui");%>
    </body>
    </html>
    

    服务器tomcat2下的测试页面

    <%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
        
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>服务器2222222222222</title>
    <style type="text/css">
    </style>
    </head>
    <body>
    		<h1>服务器2的页面</h1>
    		
    		服务器2创建Session的Id:<%=session.getId() %><br/>
    		
    		服务器1创建的Session中的存入的username:<%session.getAttribute("username");%>
    </body>
    </html>
    

    启动tomcat1,tomcat2,nginx测试

    当访问tomcat1页面时,在这里插入图片描述

    刷新页面,访问tomcat2页面:

    在这里插入图片描述

    ****得出结论:使用Nginx访问服务器,由于访问的网址不会发生,所以Cookie中的信息在集群的服务器之间是共享的

    根据得出结论,当用户第一次访问服务时,服务器创建Session,这时服务器把Session存放到Redis当中,交由Redis管理。以SessionID作为key,Session对象作为值。当用户再次访问时,不管访问的哪一个服务器,Cookie都会带着这个SessionID过来,根据这个SessionID就可以在Redis中找到对应的Session,这样就实现了Session共享。也就是下面我们要说到的这个方案。

    使用nginx+tomcat+redis完成session共享(推荐方式)

    在这里插入图片描述

    redis安装:https://blog.csdn.net/qq_41258204/article/details/83715582

    下载tomcat-redis-session-manager相应的jar包,主要有三个:

    在这里插入图片描述

    commons-pool-1.6.jar :主要是来用管理redis
    jedis-2.1.0.jar :用来操作redis的客户端
    tomcat-redis-session-manager:主要使用redis来管理服务器创建的Session

    这三个jar包,网上不太容易找到,好多下载都要收费。而且下载之后还要考虑三个jar之间的兼容性。我把测试成功的这三个jar包放在文章末尾的示例项目中!

    把这三个jar复制到tomcat的lib目录下,
    在这里插入图片描述

    修改tomcat/conf目录下的context.xml,在这个文件中,加入下面配置

    	<Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve" />
    <Manager className="com.radiadesign.catalina.session.RedisSessionManager"
             host="localhost"
             port="6379"
             database="0"
             maxInactiveInterval="60" />
    

    注意:所有集群的tomcat都要进行上述的配置。

    启动Redis,tomcat1,tomcat2,Nginx服务,进行测试

    在这里插入图片描述

    用户再一次刷新页面,

    在这里插入图片描述

    我们还可以通过redis的客户端查看:
    在这里插入图片描述

    由于使用redis的客户端查看,对数据进行了转码,所以会显示成上面的结果。

    tomcat的广播机制,完成session共享(不推荐)

    修改tomcat/conf下的server.xml文件
    在这里插入图片描述

    把这个注释的部分去掉

     <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
    

    在项目的web.xml中配置

    <distributable/>
    

    在集群的tomcat上配置好以后,重启tomcat即可。

    根据用户的IP进行hash计算,让用户访问指定的tomcat

    修改Nginx/conf目录下的nginx.conf文件

    	#下面这个配置需要自己添加
    	upstream xiaogui_server{
    		server localhost:8080;
    		server 127.0.0.1:8888;
    		ip_hash;
    	}
    

    在上面这个配置中加入ip_hash;即可。

    缺点:1.由于局域网对外的IP都是一样的,如果该局域网中的用户都对服务进行访问,根据这个配置会造成局域网内所有的用户都会访问该服务器,这时服务器的访问压力会很大。2.当这个服务器宕机之后,可能被分配到这个服务器上的用户就没有办法进行访问了。

    Nginx第三方模块upstream_hash解决session共享

    为了解决ip_hash的一些问题,可以使用upstream_hash这个第三方模块,这个模块多数情况下是用作url_hash的,但是并不妨碍将它用来做session共享:

    假如前端是squid,他会将ip加入x_forwarded_for这个http_header里,用upstream_hash可以用这个头做因子,将请求定向到指定的后端:

    hash $http_x_forwarded_for;
    

    在nginx新版本中可支持读取cookie值,所以也可以改成:

    hash $cookie_jsessionid;
    

    最后这种没有做过测试,有兴趣的话,自己可以测试一下。

    分享示例项目在码云上的地址:https://gitee.com/xiaoguixiaogege/ClusterShareSession

    展开全文
  • 使用VM虚拟 server服务器集群进行搭建学习,但是考虑到本机笔记本的配置,不知道是扩展内存还是重新购买台式机? 我本机是宏碁 E471 系列的,4G内存,i5的U,安装一台虚拟机就感觉有点卡,多台的话不知扩展内存...
  • 应用服务器集群的session管理:这是我在一本网站技术架构分析的书上看到的。这一章主要写在高可用架构设计之下服务无状态这一特性下怎么管理会话(session)。以下是几例解决方案(示例图为转载图片): 1....
  • 请问生产环境上面一个集群机器内通信可以用hosts文件替代dns服务器吗?比如我在服务器上跑的项目连接数据库等等的连接都可以用hosts文件吗</p>
  • 超强应用服务器集群方案,针对集群开发程序,解决多并发等常见服务器问题
  • 当使用多台服务器架设成集群之后,我们通过负载均衡的方式,同一个用户(或者ip)访问时被分配到不同的服务器上,假设在A服务器登录,如果在B服务器拿不到用户的登录信息session。这时访问到B服务器时就出现未登录...
  • 解决应用服务器集群后session问题 一. 何为session 用户使用网站的服务,基本上需要浏览器和web服务器进行多次交互,web服务器如何知道哪些请求是来自哪个会话的? 具体方式为:在会话开始时,分配一个唯一的会话...
  • session是产生于服务器端用来保存用户和服务器会话状态的。而传统的单击型web服务器,...问题:由于集群,用户访问的服务器可能存取不确定性,如何保证用户的session不发生丢失和异常,保证会话都是关联到同一个sessio
  • 过程中也是遇到了很多问题,这里进行记录一下 先介绍一下目前我搭建的服务器各个功能 我这边是3个地址分别是27,28,29 其中27是别人搭建好的给我的(搭建完成以后测试client能上传图片就给了我) 后面的28,29是我自己...
  • 服务器集群同步session的问题

    千次阅读 2011-03-01 11:14:00
    在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果 session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态。所以...
  • 应用服务器集群下的session保持问题

    千次阅读 2016-03-20 12:02:17
    在单服务器环境中,不要考虑session问题,但是单服务器宕机的情况会导致服务中断,这是很多对应用服务可用性要求较高的客户所不希望的。所以很多客户都会提出 应用服务集群。 用两台及以上服务器组件集群,一般客户...
  • 解决应用服务器集群的Session问题

    千次阅读 2017-12-01 17:37:32
    用户使用网站的服务,基本上需要浏览器与Web服务器的多次交互。HTTP协议本身是无状态的,需要基于HTTP协议支持会话状态(Session State)的机制。而这样的机制应该可以使Web服务器从多次单独的HTTP请求中看到...
  • 服务器集群中服务器之间如何通信

    万次阅读 2018-08-13 17:26:18
    服务器集群中服务器之间如何通信? 网上看到好多人是通过以下方式去做的: 在做服务器集群时,集群中的服务器需要通信,比如Client1(简称C1)连接到Server1(简称S1),Client2连接到Server2,Client1需要向...
  • 随着互联网应用的用户量不断激增,并发的需求越来越受到开发者的关注,通过集群的方式来解决web的瓶颈。但是集群的session共享是个...即使两次请求在集群中的两台服务器上完成,也可以到达session共享.这种解决方法的优
  • 服务器集群中,定时任务设计需要解决的问题: 1、如果集群中每台机器都启动定时任务,容易造成数据重复处理的问题。 2、如果采用定时任务开关的方式,只一台机器的开关on,其他机器的开关off,可以避免数据重复处理...
  • 解决应用服务器集群后session问题

    千次阅读 2015-05-02 10:23:50
    用户使用网站的服务,基本上需要浏览器和web服务器进行多次交互,web服务器如何知道哪些请求是来自哪个会话的? 具体方式为:在会话开始时,分配一个唯一的会话标识(sessionId),通过cookie把这个标识告诉浏览器,...
  • Tomcat服务器集群搭建

    万次阅读 2018-05-23 15:10:06
    Tomcat服务器集群与负载均衡一、前言在单一的服务器上执行WEB应用程序有一些重大的问题,当网站成功建成并开始接受大量请求时,单一服务器终究无法满足需要处理的负荷量,所以就有点显得有 点力不从心了。...
  • 让同样的Session 的请求每次都发送到同一个服务器端处理,利于针对Session 进行服务器端本地的缓存。 缺点: 1、如果其中一台宕机,会话数据将丢失。 2、会话标识是应用层的信息,那么负载均衡器要将同一个会话的...
  • 服务器集群技术(备份服务器方案和均摊工作方案)(用来解决服务器挂掉问题) 一、总结 1、在一个集群里面,比如老大因为莫名其妙的原因挂掉了,集群监测到老大挂掉了直接给他断掉电源(等待维修),然后让老二上...
  • 服务器集群cluster讲解

    2020-08-12 14:21:37
    对于大型任务,由于一台服务器无法按时完成,所以就要把大型任务拆分成许多中小型任务,然后再分配给多台服务器,由它们协同完成,这就是计算机集群技术所要解决的问题。 对租户来说,由很多台服务器组成的集群系统...
  • Nginx与服务器集群

    千次阅读 2018-09-29 00:37:43
    服务器的集合 解决实际网站运营过程中,出现的大流量、大并发的问题。解决整个网站架构,出现的突发问题。提高网站的可用性和稳定性。 服务器的概念: ①硬件 性能比较好的电脑主机 刀片机 ②软件 能够某种或者...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,401
精华内容 3,760
关键字:

服务器集群问题