整合apache+tomcat+keepalived實現高可用tomcat集群


Apache是一個強大的Web服務器在處理靜態頁面、處理大量網絡客戶請求、支持服務的種類以及可配置方面都有優勢,高速並且強壯。但是沒有JSP/Servlet的解析能力。整合Apache和Tomcat可以看作是用Tomcat做Apache的jsp/servlet解析插件,將兩者優勢結合起來。不過Tomcat作為一個Web服務器,本身具備了基本的Web服務功能,在SUN的力推下,將來或許越來越強壯到不需要借助Apache優勢的地步。

Tomcat是一個免費的開放源代碼的Web 應用服務器,屬於輕量級應用服務器,在中小型系統和並發訪問用戶不是很多的場合下被普遍使用,是開發和調試JSP 程序的首選。Tomcat 很受廣大程序員的喜歡,因為它運行時占用的系統資源小,擴展性好,支持負載平衡與郵件服務等開發應用系統常用的功能;而且它還在不斷的改進和完善中,任何一個感興趣的程序員都可以更改它或在其中加入新的功能。

Apache與tomcat的異同:

apache支持靜態頁,tomcat支持動態的,比如servlet等。

一般使用apache+tomcat的話,apache只是作為一個轉發,對jsp的處理是由tomcat來處理的。

apache可以支持php\cgi\perl,但是要使用java的話,你需要tomcat在apache后台支撐,將java請求由apache轉發給tomcat處理。

apache是web服務器,Tomcat是應用(java)服務器,它只是一個servlet(jsp也翻譯成servlet)容器,可以認為是apache的擴展,但是可以獨立於apache運行。

Apache是專門用了提供HTTP服務的,以及相關配置的(例如虛擬主機、URL轉發等等)。

Tomcat是Apache組織在符合J2EE的JSP、Servlet標准下開發的一個JSP服務器;

Apache與tomcat整合的好處

如果客戶端請求的是靜態頁面,則只需要Apache服務器響應請求。

如果客戶端請求動態頁面,則是Tomcat服務器響應請求。

因為jsp是服務器端解釋代碼的,這樣整合就可以減少Tomcat的服務開銷。

 

一.環境與功能說明

前端:apache配置負載均衡、反向代理(mod_jk模塊)

后端:tomcat服務器

前端高可用:通過keepalived實現

 

系統版本:CentOS 6.4_x86_64(最小化)

apache版本:httpd-2.4.9

arp版本:apr-1.4.6

apr-util版本:apr-util-1.4.1

tomcat版本:apache-tomcat-7.0.53

Jdk版本:jdk-7u40-linux-x64

tomcat-commectors版本:tomcat-connectors-1.2.40

keepalived版本:keepalived-1.2.7-3.el6.x86_64(使用yum安裝)

 

二.安裝與配置apache服務器(172.16.10.72、172.16.10.75執行同樣的安裝與配置,這里只演示一次)

1.安裝編譯工具和依賴包

yum groupinstall "Development Tools" "Development Libraries" –y

2.下載軟件包

yum -y install wget
wget http://mirror.esocc.com/apache//httpd/httpd-2.4.9.tar.gz
wget http://mirrors.axint.net/apache//apr/apr-1.4.6.tar.gz
wget http://mirrors.axint.net/apache//apr/apr-util-1.4.1.tar.gz

3.安裝apache

3.1.安裝apr和apr-tuil

httpd-2.4.9需要較新版本的apr和apr-util,因此需要事先對其進行升級。升級方式有兩種,一種是通過源代碼編譯安裝,一種是直接升級rpm包。以下是采用前者進行安裝:

# tar xf apr-1.4.6.tar.gz # tar xf apr-util-1.4.1.tar.gz # cd apr-1.4.6 # ./configure --prefix=/usr/local/apr # make # make install # cd .. # cd apr-util-1.4.1 # ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr # make # make install # cd ..

3.2.安裝http-2.4.9

# ./configure --prefix=/usr/local/apache --sysconfdir=/etc/httpd24 --enable-so --enable-ssl --enable-cgi --enable-rewrite --with-zlib --with-pcre --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util/ --enable-modules=most --enable-mpms-shared=all # make && make install

4.配置SysV服務腳本/etc/init.d/httpd

# cp build/rpm/httpd.init /etc/init.d/httpd #修改如下內容: 
#
vi /etc/init.d/httpd httpd=${HTTPD-/usr/local/apache/bin/httpd} pidfile=${PIDFILE-/usr/local/apache/logs/${prog}.pid} lockfile=${LOCKFILE-/var/lock/subsys/${prog}} RETVAL=0 # check for 1.3 configuration check13 () { CONFFILE=/etc/httpd24/httpd.conf


#修改環境變量並把Httpd加入到系統服務

  # echo "PATH=/usr/local/apache/bin:$PATH" >> /etc/profile.d/http.sh
  # . /etc/profile.d/http.sh
  # ln -s /usr/local/apache/include/ /usr/include/httpd
  # chkconfig --add httpd

 5.安裝tomcat-connectors

# wget http://apache.fayea.com/apache-mirror/tomcat/tomcat-connectors/jk/tomcat-connectors-1.2.40-src.tar.gz
# tar xf tomcat-connectors-1.2.40-src.tar.gz # cd tomcat-connectors-1.2.40-src/native/ # ./configure --with-apxs=/usr/local/apache/bin/apxs # make # make install

安裝完成后會生成如下文件

# ls /usr/local/apache/modules/|grep 'mod_jk' mod_jk.so

啟動httpd服務

# service httpd start

執行./configure --with-apxs=/usr/local/apache/bin/apxs時可能會遇到以下報錯:

need to check for Perl first, apxs depends on it...
checking for perl... /usr/bin/perl
could not find /usr/local/apache/bin/apxs
configure: error: You must specify a valid --with-apxs path

解法辦法:

vi /usr/local/apache/bin/apxs
把第一行改成:#!/usr/bin/perl -w

另外檢查系統中是否已經安裝過perl,如果沒有安裝,用yum安裝即可。

三.安裝與配置tomcat服務器(172.16.10.61、172.16.10.62執行同樣的安裝,這里只演示一次)

1.安裝JDK

下載地址:http://download.oracle.com/

# rpm -ivh jdk-7u40-linux-x64.rpm # vim /etc/profile.d/java.sh export JAVA_HOME=/usr/java/latest export PATH=$JAVA_HOME/bin:$PATH # . /etc/profile.d/java.sh # java -version java version "1.7.0_40" Java(TM) SE Runtime Environment (build 1.7.0_40-b43) Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)

2.安裝Tomcat

下載地址:http://apache.fayea.com/apache-mirror/tomcat/tomcat-7/v7.0.53/bin/apache-tomcat-7.0.53.tar.gz

# tar xf apache-tomcat-7.0.53.tar.gz -C /usr/local/ # cd /usr/local/ # ln -s apache-tomcat-7.0.53 tomcat # vim /etc/profile.d/tomcat.sh export CATALINA_HOME=/usr/local/tomcat export PATH=$CATALINA_HOME/bin:$PATH # . /etc/profile.d/tomcat.sh # /usr/local/tomcat/bin/catalina.sh version Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/java/latest Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Server version: Apache Tomcat/7.0.53 Server built: Mar 25 2014 06:20:16 Server number: 7.0.53.0 OS Name: Linux OS Version: 2.6.32-279.el6.x86_64 Architecture: amd64 JVM Version: 1.7.0_40-b43 JVM Vendor: Oracle Corporation

3.啟動Tomcat服務並訪問測試,默認訪問端口為8080:

# /usr/local/tomcat/bin/catalina.sh start Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/java/latest Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Tomcat started. # ss -antpl | grep 8080
0      100                           :::8080                         :::*      users:(("java",1299,42))

4.開啟Tomcat管理及狀態頁面

# vim /usr/local/tomcat/conf/tomcat-users.xml(在</tomcat-users>標簽上一行添加如下兩行) <role rolename="manager-gui"/>
  <user username="tomcat" password="tomcat" roles="manager-gui"/> #后台管理頁面登錄名及密碼均為tomcat # /usr/local/tomcat/bin/catalina.sh stop # /usr/local/tomcat/bin/catalina.sh start

5.訪問Tomcat后台管理

四.配置Apache使用mod_jk模塊實現代理及負載均衡

1.編輯Apache主配置文件,實現代理功能(172.16.10.72)

echo "Include /etc/httpd24/extra/mod_jk.conf" >> /etc/httpd24/httpd.conf  #將此指令添加到文件末尾即可

2.創建mod_jk.conf及workers.properties文件

# cat >>/etc/httpd24/extra/mod_jk.conf <<EOF LoadModule jk_module modules/mod_jk.so JkWorkersFile /etc/httpd24/extra/workers.properties JkLogFile logs/mod_jk.log JkLogLevel debug JkMount /* TomcatA JkMount /status/ stat1 EOF # cat >>/etc/httpd24/extra/workers.properties <<EOF worker.list=TomcatA,stat1 worker.TomcatA.port=8009 worker.TomcatA.host=172.16.10.61 worker.TomcatA.type=ajp13 worker.TomcatA.lbfactor=1 worker.stat1.type = status EOF

3.編輯httpd.conf修改ServerName並重啟httpd服務

#vi /etc/httpd24/httpd.conf
ServerName 172.16.10.72:80
# service httpd restart

4.修改tomcat服務器(172.16.10.61),添加測試頁面

# cd /usr/local/tomcat/conf/ # cp server.xml server.xml.bak # vi server.xml <Engine name="Catalina" defaultHost="172.16.10.72">
<Host name="172.16.10.72"  appBase="/web/eivll0m" unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="eivll0m_access_log." suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
        <Context path="" docBase="/web/eivll0m" />
      </Host>

創建網站存放目錄並創建測試頁

# mkdir -p /web/eivll0m # vi /web/eivll0m/index.jsp <%@ page language="java" %>
<html>
  <head><title>TomcatA</title></head>
  <body>
    <h1><font color="red">TomcatA </font></h1>
    <table align="centre" border="1">
      <tr>
        <td>Session ID</td>
    <% session.setAttribute("abc","abc"); %>
        <td><%= session.getId() %></td>
      </tr>
      <tr>
        <td>Created on</td>
        <td><%= session.getCreationTime() %></td>
     </tr>
    </table>
  </body>
</html>

重啟tomcat服務並查看是否啟動成功

# catalina.sh stop # catalina.sh configtest # catalina.sh start # ss -antpl | grep java 0      1               ::ffff:127.0.0.1:8005                         :::*      users:(("java",1299,50)) 0      100                           :::8009                         :::*      users:(("java",1299,43)) 0      100                           :::8080                         :::*      users:(("java",1299,42))

5.訪問Apache主機(172.16.10.72),驗證是否代理成功

 6.修改Apache配置文件(mod_jk.conf、workers.properties),實現負載均衡

# cat >>/etc/httpd24/extra/mod_jk.conf <<EOF LoadModule jk_module modules/mod_jk.so JkWorkersFile /etc/httpd24/extra/workers.properties JkLogFile logs/mod_jk.log JkLogLevel debug JkMount /* lbcluster JkMount /status/ stat1 EOF 
# cat >>/etc/httpd/extra/workers.properties <<EOF worker.list=lbcluster,stat1 worker.TomcatA.port=8009 worker.TomcatA.host=172.16.10.61 worker.TomcatA.type=ajp13 worker.TomcatA.lbfactor=1 worker.TomcatB.port = 8009 worker.TomcatB.host=172.16.10.62 worker.TomcatB.type = ajp13 worker.TomcatB.lbfactor = 1 worker.lbcluster.type = lb worker.lbcluster.sticky_session = 0 #取值為{1|0}1將用戶session與后端服務器綁定,0為不綁定,如果支持session復制或session共享可以設置為0 worker.lbcluster.balance_workers = TomcatA, TomcatB worker.stat1.type = status EOF

7.修改tomcat服務器(172.16.10.62),添加測試頁面

# cd /usr/local/tomcat/conf/ # cp server.xml server.xml.bak # vi server.xml <Engine name="Catalina" defaultHost="172.16.10.72">
<Host name="172.16.10.72"  appBase="/web/eivll0m" unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="eivll0m_access_log." suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
        <Context path="" docBase="/web/eivll0m" />
      </Host>

創建網站存放目錄並創建測試頁

# mkdir -p /web/eivll0m # vi /web/eivll0m/index.jsp <%@ page language="java" %>
<html>
  <head><title>TomcatB</title></head>
  <body>
    <h1><font color="blue">TomcatB </font></h1>
    <table align="centre" border="1">
      <tr>
        <td>Session ID</td>
    <% session.setAttribute("abc","abc"); %>
        <td><%= session.getId() %></td>
      </tr>
      <tr>
        <td>Created on</td>
        <td><%= session.getCreationTime() %></td>
     </tr>
    </table>
  </body>
</html>

重啟tomcat服務並查看是否啟動成功

# catalina.sh stop # catalina.sh configtest # catalina.sh start # ss -antpl | grep java 0      1               ::ffff:127.0.0.1:8005                         :::*      users:(("java",1303,50)) 0      100                           :::8009                         :::*      users:(("java",1303,43)) 0      100                           :::8080                         :::*      users:(("java",1303,42))

8.訪問Apache服務器(172.16.10.72),測試是否實現負載均衡(如下表示測試成功)

五.安裝並配置Keepalived實現高可用

1.在apache服務器(172.16.10.72、172.16.10.75)安裝keepalived

yum -y install keepalived

2.配置Keepalived(172.16.10.72)

# vi /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from admin@eivll0m.com smtp_server 172.16.10.0 smtp_connect_timeout 30 router_id LVS_DEVEL } vrrp_script chk_httpd { script "killall -0 httpd" interval 1 weight -2 } vrrp_instance httpd_1 { state MASTER interface eth0 virtual_router_id 58 priority 100 #定義優先級 advert_int 1 authentication { auth_type PASS auth_pass 1058 } virtual_ipaddress { 172.16.10.77 #vip } track_script { chk_httpd } }

# service keepalived start #啟動keepalived服務
# chkconfig keepalived on

3.配置Keepalived(172.16.10.75)

# vi /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from admin@eivll0m.com smtp_server 172.17.10.0 smtp_connect_timeout 30 router_id LVS_DEVEL } vrrp_script chk_httpd { script "killall -0 httpd" interval 1 weight -2 } vrrp_instance httpd_1 { state BACKUP interface eth0 virtual_router_id 58 priority 99 #定義優先級 advert_int 1 authentication { auth_type PASS auth_pass 1058 } virtual_ipaddress { 172.16.10.77 #vip } track_script { chk_httpd } }
# service keepalived start  
# chkconfig keepalived on

4.編輯httpd.conf(172.16.10.72),並拷貝至172.16.10.75

# vi /etc/httpd24/httpd.conf ServerName www.eivll0m.com:80 # service httpd restart # cd /etc/httpd24/ # scp httpd.conf 172.16.10.75:/etc/httpd24/ # cd extra/ # scp mod_jk.conf workers.properties 172.16.10.75:/etc/httpd24/extra/
# service httpd restart #兩台apache服務器分別執行

 5.編輯tomcat服務器(172.17.10.61和172.16.10.62)的server.xml

# vi server.xml <Engine name="Catalina" defaultHost="www.eivll0m.com">
<Host name="www.eivll0m.com"  appBase="/web/eivll0m" unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="eivll0m_access_log." suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
        <Context path="" docBase="/web/eivll0m" />
      </Host>
# catalina.sh stop
# catalina.sh start

5.通過www.eivll0m.com(172.16.10.77)進行訪問測試

注意:測試時關閉所有服務器iptables,如果沒有做DNS解析,可在測試機上修改hosts文件來實現。

 

6.模擬一台apache故障,驗證keepalived是否可以成功轉移服務

# ip addr show eth0   #172.16.10.72上執行 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:e9:b8:52 brd ff:ff:ff:ff:ff:ff inet 172.16.10.72/24 brd 172.16.10.255 scope global eth0 inet 172.16.10.77/32 scope global eth0 inet6 fe80::20c:29ff:fee9:b852/64 scope link tentative dadfailed valid_lft forever preferred_lft forever # ip addr show eth0 #172.16.10.75上執行 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:c8:1a:08 brd ff:ff:ff:ff:ff:ff inet 172.16.10.75/24 brd 172.16.10.255 scope global eth0 inet 192.168.200.16/32 scope global eth0 inet 192.168.200.17/32 scope global eth0 inet 192.168.200.18/32 scope global eth0 inet6 fe80::20c:29ff:fec8:1a08/64 scope link tentative dadfailed valid_lft forever preferred_lft forever # service httpd stop #停止172.16.10.72上的httpd服務 # ip addr show eth0 #172.16.10.72上執行 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:e9:b8:52 brd ff:ff:ff:ff:ff:ff inet 172.16.10.72/24 brd 172.16.10.255 scope global eth0 inet6 fe80::20c:29ff:fee9:b852/64 scope link tentative dadfailed valid_lft forever preferred_lft forever # ip addr show eth0 #172.16.10.75上執行 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:c8:1a:08 brd ff:ff:ff:ff:ff:ff inet 172.16.10.75/24 brd 172.16.10.255 scope global eth0 inet 192.168.200.16/32 scope global eth0 inet 192.168.200.17/32 scope global eth0 inet 192.168.200.18/32 scope global eth0 inet 172.16.10.77/32 scope global eth0 #vip已經成功轉移過來 inet6 fe80::20c:29ff:fec8:1a08/64 scope link tentative dadfailed valid_lft forever preferred_lft forever

通過上面的步驟我們可以看到,在172.16.10.72上停止apache服務,keepalived會檢測到,另一台keepalived服務器會接管vip,web服務不會中斷,從而實現了高可用性。

7.配置tomcat實現session共享

7.1.配置172.16.10.61這台tomcat服務器

 

# vi /usr/local/tomcat/conf/server.xml  #在<Engine>標簽內(對所有主機生效)添加或修改如下內容,另外也可以添加在<Host>標簽內,對當前主機生效。並去掉此段前后注釋
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="8">
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="224.0.0.1"   #組播地址
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="172.16.10.61"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>
            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>
          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>
          <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

 

# mkdir /web/eivll0m/WEB-INF
# cp /usr/local/tomcat/conf/web.xml /web/eivll0m/WEB-INF/
# vim /web/eivll0m/WEB-INF/web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">
<distributable/>                      #添加此行內容
# catalina.sh stop 
# catalina.sh start
# ss -tanlp | grep java
0      1               ::ffff:127.0.0.1:8005                         :::*      users:(("java",1935,55))
0      100                           :::8009                         :::*      users:(("java",1935,43))
0      100                           :::8080                         :::*      users:(("java",1935,42))
0      50           ::ffff:172.16.10.61:4000                         :::*      users:(("java",1935,44))

7.2.配置172.16.10.62這台tomcat服務器

# scp /usr/local/tomcat/conf/server.xml 172.16.10.62:/usr/local/tomcat/conf/
# scp -r /web/eivll0m/WEB-INF 172.16.10.62:/web/eivll0m/
# vi /usr/local/tomcat/conf/server.xml
                      address="172.16.10.62"   #更改IP
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>
# catalina.sh stop 
# catalina.sh start
# ss -antpl|grep java    
0      1               ::ffff:127.0.0.1:8005                         :::*      users:(("java",2041,61))
0      100                           :::8009                         :::*      users:(("java",2041,43))
0      100                           :::8080                         :::*      users:(("java",2041,42))
0      50           ::ffff:172.16.10.62:4000                         :::*      users:(("java",2041,44))

注:因為tomcat的session同步功能需要用到組播,windows默認情況下是開通組播服務的,但是linux默認情況下並沒有開通,可以通過指令打開route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0,如果需要服務器啟動時即開通組播需在/etc/sysconfig/static-routes文件內加入eht0 net 224.0.0.0 netmask 240.0.0.0。

 

7.3.驗證session共享是否實現

 

通過以上截圖可以看出session共享已經實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM