`
jinnianshilongnian
  • 浏览: 21504019 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2418675
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3008772
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5639488
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:259916
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1597311
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:250216
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5858947
Group-logo
跟我学Nginx+Lua开...
浏览量:702000
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:785223
社区版块
存档分类
最新评论

第六章 Web开发实战1——HTTP服务

阅读更多

此处我说的HTTP服务主要指如访问京东网站时我们看到的热门搜索、用户登录、实时价格、实时库存、服务支持、广告语等这种非Web页面,而是在Web页面中异步加载的相关数据。这些服务有个特点即访问量巨大、逻辑比较单一;但是如实时库存逻辑其实是非常复杂的。在京东这些服务每天有几亿十几亿的访问量,比如实时库存服务曾经在没有任何IP限流、DDos防御的情况被刷到600多万/分钟的访问量,而且能轻松应对。支撑如此大的访问量就需要考虑设计良好的架构,并很容易实现水平扩展。

 

架构

此处介绍下我曾使用过Nginx+JavaEE的架构。

 

1、单DB架构

早期架构可能就是Nginx直接upstream请求到后端Tomcat,扩容时基本是增加新的Tomcat实例,然后通过Nginx负载均衡upstream过去。此时数据库还不是瓶颈。当访问量到一定级别,数据库的压力就上来了,此处单纯的靠单个数据库可能扛不住了,此时可以通过数据库的读写分离或加缓存来实现。

 

 

2、DB+Cache/数据库读写分离架构


 

此时就通过使用如数据库读写分离或者Redis这种缓存来支撑更大的访问量。使用缓存这种架构会遇到的问题诸如缓存与数据库数据不同步造成数据不一致(一般设置过期时间),或者如Redis挂了,此时会直接命中数据库导致数据库压力过大;可以考虑Redis的主从或者一致性Hash 算法做分片的Redis集群;使用缓存这种架­构要求应用对数据的一致性要求不是很高;比如像下订单这种要落地的数据不适合用Redis存储,但是订单的读取可以使用缓存。

 

3、Nginx+Lua+Local Redis+Mysql集群架构

首先Nginx通过Lua读取本机Redis缓存,如果不命中才回源到后端Tomcat集群;后端Tomcat集群再读取Mysql数据库。Redis都是安装到和Nginx同一台服务器,Nginx直接读本机可以减少网络延时。Redis通过主从方式同步数据,Redis主从一般采用树的方式实现:

在叶子节点可以做AOF持久化,保证在主Redis挂时能进行恢复;此处假设对Redis很依赖的话,可以考虑多主Redis架构,而不是单主,来防止单主挂了时数据的不一致和击穿到后端Tomcat集群。这种架构的缺点就是要求Redis实例数据量较小,如果单机内存不足以存储这么多数据,当然也可以通过如尾号为1的在A服务器,尾号为2的在B服务器这种方式实现;缺点也很明显,运维复杂、扩展性差。

 

4、Nginx+Lua+ Redis集群+Mysql集群架构 

和之前架构不同的点是此时我们使用一致性Hash算法实现Redis集群而不是读本机Redis,保证其中一台挂了,只有很少的数据会丢失,防止击穿到数据库。Redis集群分片可以使用Twemproxy;如果 Tomcat实例很多的话,此时就要考虑Redis和Mysql链接数问题,因为大部分Redis/Mysql客户端都是通过连接池实现,此时的链接数会成为瓶颈。一般方法是通过中间件来减少链接数。

Twemproxy与Redis之间通过单链接交互,并Twemproxy实现分片逻辑;这样我们可以水平扩展更多的Twemproxy来增加链接数。

 

此时的问题就是Twemproxy实例众多,应用维护配置困难;此时就需要在之上做负载均衡,比如通过LVS/HAProxy实现VIP(虚拟IP),可以做到切换对应用透明、故障自动转移;还可以通过实现内网DNS来做其负载均衡。

本文没有涉及Nginx之上是如何架构的,对于Nginx、Redis、Mysql等的负载均衡、资源的CDN化不是本文关注的点,有兴趣可以参考

很早的Taobao CDN架构

Nginx/LVS/HAProxy负载均衡软件的优缺点详解 

 

实现

接下来我们来搭建一下第四种架构。

以获取如京东商品页广告词为例,如下图

假设京东有10亿商品,那么广告词极限情况是10亿;所以在设计时就要考虑:

1、数据量,数据更新是否频繁且更新量是否很大;

2、是K-V还是关系,是否需要批量获取,是否需要按照规则查询。

 

而对于本例,广告词更新量不会很大,每分钟可能在几万左右;而且是K-V的,其实适合使用关系存储;因为广告词是商家维护,因此后台查询需要知道这些商品是哪个商家的;而对于前台是不关心商家的,是KV存储,所以前台显示的可以放进如Redis中。 即存在两种设计:

1、所有数据存储到Mysql,然后热点数据加载到Redis;

2、关系存储到Mysql,而数据存储到如SSDB这种持久化KV存储中。

 

基本数据结构:商品ID、广告词、所属商家、开始时间、结束时间、是否有效。

 

后台逻辑

1、商家登录后台;

2、按照商家分页查询商家数据,此处要按照商品关键词或商品类目查询的话,需要走商品系统的搜索子系统,如通过Solr或elasticsearch实现搜索子系统;

3、进行广告词的增删改查;

4、增删改时可以直接更新Redis缓存或者只删除Redis缓存(第一次前台查询时写入缓存);

 

前台逻辑

1、首先Nginx通过Lua查询Redis缓存;

2、查询不到的话回源到Tomcat,Tomcat读取数据库查询到数据,然后把最新的数据异步写入Redis(一般设置过期时间,如5分钟);此处设计时要考虑假设Tomcat读取Mysql的极限值是多少,然后设计降级开关,如假设每秒回源达到100,则直接不查询Mysql而返回空的广告词来防止Tomcat应用雪崩。

 

为了简单,我们不进行后台的设计实现,只做前端的设计实现,此时数据结构我们简化为[商品ID、广告词]。另外有朋友可能看到了,可以直接把Tomcat部分干掉,通过Lua直接读取Mysql进行回源实现。为了完整性此处我们还是做回源到Tomcat的设计,因为如果逻辑比较复杂的话或一些限制(比如使用Java特有协议的RPC)还是通过Java去实现更方便一些。

 

项目搭建

项目部署目录结构。

/usr/chapter6
  redis_6660.conf
  redis_6661.conf
  nginx_chapter6.conf
  nutcracker.yml
  nutcracker.init
  webapp
WEB-INF
   lib
   classes
   web.xml 

 

Redis+Twemproxy配置

此处根据实际情况来决定Redis大小,此处我们已两个Redis实例(6660、6661),在Twemproxy上通过一致性Hash做分片逻辑。

 

安装

之前已经介绍过Redis和Twemproxy的安装了。

 

Redis配置redis_6660.conf和redis_6661.conf    

#分别为6660 6661
port 6660
#进程ID 分别改为redis_6660.pid redis_6661.pid
pidfile "/var/run/redis_6660.pid"
#设置内存大小,根据实际情况设置,此处测试仅设置20mb
maxmemory 20mb
#内存不足时,按照过期时间进行LRU删除
maxmemory-policy volatile-lru
#Redis的过期算法不是精确的而是通过采样来算的,默认采样为3个,此处我们改成10
maxmemory-samples 10
#不进行RDB持久化
save “”
#不进行AOF持久化
appendonly no 

将如上配置放到redis_6660.conf和redis_6661.conf配置文件最后即可,后边的配置会覆盖前边的。  

 

Twemproxy配置nutcracker.yml 

server1:
  listen: 127.0.0.1:1111
  hash: fnv1a_64
  distribution: ketama
  redis: true
  timeout: 1000
  servers:
   - 127.0.0.1:6660:1 server1
   - 127.0.0.1:6661:1 server2

 复制nutcracker.init到/usr/chapter6下,并修改配置文件为/usr/chapter6/nutcracker.yml。

 

启动

nohup /usr/servers/redis-2.8.19/src/redis-server  /usr/chapter6/redis_6660.conf &
nohup /usr/servers/redis-2.8.19/src/redis-server  /usr/chapter6/redis_6661.conf &
/usr/chapter6/nutcracker.init start
ps -aux | grep -e redis  -e nutcracker

  

Mysql+Atlas配置

Atlas类似于Twemproxy,是Qihoo 360基于Mysql Proxy开发的一个Mysql中间件,据称每天承载读写请求数达几十亿,可以实现分表、分库(sharding版本)、读写分离、数据库连接池等功能,缺点是没有实现跨库分表功能,需要在客户端使用分库逻辑,目前Atlas不活跃。另一个选择是使用如阿里的TDDL,它是在客户端完成之前说的功能。到底选择是在客户端还是在中间件根据实际情况选择。

 

此处我们不做Mysql的主从复制(读写分离),只做分库分表实现。

 

Mysql初始化

 

为了测试我们此处分两个表。

 

CREATE DATABASE chapter6 DEFAULT CHARACTER SET utf8;
use chapter6;
CREATE TABLE  chapter6.ad_0(
      sku_id BIGINT,
      content VARCHAR(4000)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
CREATE TABLE  chapter6.ad_1
      sku_id BIGINT,
      content VARCHAR(4000)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

 

Atlas安装

cd /usr/servers/
wget https://github.com/Qihoo360/Atlas/archive/2.2.1.tar.gz -O Atlas-2.2.1.tar.gz
tar -xvf Atlas-2.2.1.tar.gz
cd Atlas-2.2.1/
#Atlas依赖mysql_config,如果没有可以通过如下方式安装
apt-get install libmysqlclient-dev
#安装Lua依赖
wget http://www.lua.org/ftp/lua-5.1.5.tar.gz
tar -xvf lua-5.1.5.tar.gz
cd lua-5.1.5/
make linux && make install
#安装glib依赖
apt-get install libglib2.0-dev
#安装libevent依赖
apt-get install libevent  
#安装flex依赖
apt-get install flex
#安装jemalloc依赖
apt-get install libjemalloc-dev
#安装OpenSSL依赖
apt-get install openssl
apt-get install libssl-dev
 apt-get install libssl0.9.8

./configure --with-mysql=/usr/bin/mysql_config
./bootstrap.sh
make && make install

 

Atlas配置 

vim /usr/local/mysql-proxy/conf/chapter6.cnf
[mysql-proxy]
#Atlas代理的主库,多个之间逗号分隔
proxy-backend-addresses = 127.0.0.1:3306
#Atlas代理的从库,多个之间逗号分隔,格式ip:port@weight,权重默认1
#proxy-read-only-backend-addresses = 127.0.0.1:3306,127.0.0.1:3306
#用户名/密码,密码使用/usr/servers/Atlas-2.2.1/script/encrypt 123456加密
pwds = root:/iZxz+0GRoA=
#后端进程运行
daemon = true
#开启monitor进程,当worker进程挂了自动重启
keepalive = true
#工作线程数,对Atlas的性能有很大影响,可根据情况适当设置
event-threads = 64
#日志级别
log-level = message
#日志存放的路径
log-path = /usr/chapter6/
#实例名称,用于同一台机器上多个Atlas实例间的区分
instance = test
#监听的ip和port
proxy-address = 0.0.0.0:1112
#监听的管理接口的ip和port
admin-address = 0.0.0.0:1113
#管理接口的用户名
admin-username = admin
#管理接口的密码
admin-password = 123456
#分表逻辑
tables = chapter6.ad.sku_id.2
#默认字符集
charset = utf8 

因为本例没有做读写分离,所以读库proxy-read-only-backend-addresses没有配置。分表逻辑即:数据库名.表名.分表键.表的个数,分表的表名格式是table_N,N从0开始。

 

Atlas启动/重启/停止

/usr/local/mysql-proxy/bin/mysql-proxyd chapter6 start
/usr/local/mysql-proxy/bin/mysql-proxyd chapter6 restart
/usr/local/mysql-proxy/bin/mysql-proxyd chapter6 stop 

如上命令会自动到/usr/local/mysql-proxy/conf目录下查找chapter6.cnf配置文件。 

 

Atlas管理

通过如下命令进入管理接口

mysql -h127.0.0.1 -P1113  -uadmin -p123456 

通过执行SELECT * FROM help查看帮助。还可以通过一些SQL进行服务器的动态添加/移除。

 

Atlas客户端

通过如下命令进入客户端接口

mysql -h127.0.0.1 -P1112  -uroot -p123456
use chapter6;
insert into ad values(1 '测试1);    
insert into ad values(2, '测试2');    
insert into ad values(3 '测试3);    
select * from ad where sku_id=1;
select * from ad where sku_id=2;
#通过如下sql可以看到实际的分表结果
select * from ad_0;
select * from ad_1; 

 

此时无法执行select * from ad,需要使用如“select * from ad where sku_id=1”这种SQL进行查询;即需要带上sku_id且必须是相等比较;如果是范围或模糊是不可以的;如果想全部查询,只能挨着遍历所有表进行查询。即在客户端做查询-聚合。

 

此处实际的分表逻辑是按照商家进行分表,而不是按照商品编号,因为我们后台查询时是按照商家维度的,此处是为了测试才使用商品编号的。

 

到此基本的Atlas就介绍完了,更多内容请参考如下资料:

Mysql主从复制

http://369369.blog.51cto.com/319630/790921/

Mysql中间件介绍

http://www.guokr.com/blog/475765/

Atlas使用

http://www.0550go.com/database/mysql/mysql-atlas.html

Atlas文档

https://github.com/Qihoo360/Atlas/blob/master/README_ZH.md

 

Java+Tomcat安装

Java安装   

cd /usr/servers/
#首先到如下网站下载JDK
#http://www.oracle.com/technetwork/cn/java/javase/downloads/jdk7-downloads-1880260.html
#本文下载的是 jdk-7u75-linux-x64.tar.gz。
tar -xvf jdk-7u75-linux-x64.tar.gz
vim ~/.bashrc
在文件最后添加如下环境变量
export JAVA_HOME=/usr/servers/jdk1.7.0_75/
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export CLASSPATH=$CLASSPATH:.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib

#使环境变量生效
source ~/.bashrc

  

Tomcat安装 

cd /usr/servers/
wget http://ftp.cuhk.edu.hk/pub/packages/apache.org/tomcat/tomcat-7/v7.0.59/bin/apache-tomcat-7.0.59.tar.gz
tar -xvf apache-tomcat-7.0.59.tar.gz
cd apache-tomcat-7.0.59/
#启动	
/usr/servers/apache-tomcat-7.0.59/bin/startup.sh 
#停止
/usr/servers/apache-tomcat-7.0.59/bin/shutdown.sh
#删除tomcat默认的webapp
rm -r apache-tomcat-7.0.59/webapps/*
#通过Catalina目录发布web应用
cd apache-tomcat-7.0.59/conf/Catalina/localhost/
vim ROOT.xml

ROOT.xml

<!-- 访问路径是根,web应用所属目录为/usr/chapter6/webapp -->
<Context path="" docBase="/usr/chapter6/webapp"></Context>
#创建一个静态文件随便添加点内容
vim /usr/chapter6/webapp/index.html
#启动
/usr/servers/apache-tomcat-7.0.59/bin/startup.sh  

 

访问如http://192.168.1.2:8080/index.html能处理内容说明配置成功。

 

#变更目录结构
cd /usr/servers/
mv apache-tomcat-7.0.59 tomcat-server1
#此处我们创建两个tomcat实例
cp –r tomcat-server1 tomcat-server2
vim tomcat-server2/conf/server.xml    
#如下端口进行变更
8080--->8090
8005--->8006

     

启动两个Tomcat

/usr/servers/tomcat-server1/bin/startup.sh 
/usr/servers/tomcat-server2/bin/startup.sh 
分别访问,如果能正常访问说明配置正常。

 

http://192.168.1.2:8080/index.html

http://192.168.1.2:8090/index.html

 

如上步骤使我们在一个服务器上能启动两个tomcat实例,这样的好处是我们可以做本机的Tomcat负载均衡,假设一个tomcat重启时另一个是可以工作的,从而不至于不给用户返回响应。

 

Java+Tomcat逻辑开发

搭建项目

我们使用Maven搭建Web项目,Maven知识请自行学习。

 

项目依赖

本文将最小化依赖,即仅依赖我们需要的servlet、mysql、druid、jedis。 

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.27</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.5</version>
    </dependency>
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.5.2</version>
    </dependency>
  </dependencies>
 

核心代码

com.github.zhangkaitao.chapter6.servlet.AdServlet

public class AdServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String idStr = req.getParameter("id");
        Long id = Long.valueOf(idStr);
        //1、读取Mysql获取数据
        String content = null;
        try {
            content = queryDB(id);
        } catch (Exception e) {
            e.printStackTrace();
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
        if(content != null) {
            //2.1、如果获取到,异步写Redis
            asyncSetToRedis(idStr, content);
            //2.2、如果获取到,把响应内容返回
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write(content);
        } else {
            //2.3、如果获取不到,返回404状态码
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    private DruidDataSource datasource = null;
    private JedisPool jedisPool = null;

    {
        datasource = new DruidDataSource();
        datasource.setUrl("jdbc:mysql://127.0.0.1:1112/chapter6?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
        datasource.setUsername("root");
        datasource.setPassword("123456");
        datasource.setMaxActive(100);

        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(100);
        jedisPool = new JedisPool(poolConfig, "127.0.0.1", 1111);
    }

    private String queryDB(Long id) throws Exception {
        Connection conn = null;
        try {
            conn = datasource.getConnection();
            String sql = "select content from ad where sku_id = ?";
            PreparedStatement psst = conn.prepareStatement(sql);
            psst.setLong(1, id);
            ResultSet rs = psst.executeQuery();
            String content = null;
            if(rs.next()) {
                content = rs.getString("content");
            }
            rs.close();
            psst.close();
            return content;
        } catch (Exception e) {
            throw e;
        } finally {
            if(conn != null) {
                conn.close();
            }
        }
    }

    private ExecutorService executorService = Executors.newFixedThreadPool(10);
    private void asyncSetToRedis(final String id, final String content) {
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                Jedis jedis = null;
                try {
                    jedis = jedisPool.getResource();
                    jedis.setex(id, 5 * 60, content);//5分钟
                } catch (Exception e) {
                    e.printStackTrace();
                    jedisPool.returnBrokenResource(jedis);
                } finally {
                    jedisPool.returnResource(jedis);
                }

            }
        });
    }
}
整个逻辑比较简单,此处更新缓存一般使用异步方式去更新,这样不会阻塞主线程;另外此处可以考虑走Servlet异步化来提示吞吐量。
web.xml配置
  <servlet>
      <servlet-name>adServlet</servlet-name>
      <servlet-class>com.github.zhangkaitao.chapter6.servlet.AdServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>adServlet</servlet-name>
      <url-pattern>/ad</url-pattern>
  </servlet-mapping>

 

打WAR包 

cd D:\workspace\chapter6
mvn clean package

此处使用maven命令打包,比如本例将得到chapter6.war,然后将其上传到服务器的/usr/chapter6/webapp,然后通过unzip chapter6.war解压。

 

测试 

启动Tomcat实例,分别访问如下地址将看到广告内容:

http://192.168.1.2:8080/ad?id=1
http://192.168.1.2:8090/ad?id=1

 

nginx配置

vim /usr/chapter6/nginx_chapter6.conf 

upstream backend {
    server 127.0.0.1:8080 max_fails=5 fail_timeout=10s weight=1 backup=false;
    server 127.0.0.1:8090 max_fails=5 fail_timeout=10s weight=1 backup=false;
    check interval=3000 rise=1 fall=2 timeout=5000 type=tcp default_down=false;
    keepalive 100;
}
server {
    listen       80;
    server_name  _;

    location ~ /backend/(.*) {
        keepalive_timeout   30s;
        keepalive_requests  100;

        rewrite /backend(/.*) $1 break;
        #之后该服务将只有内部使用,ngx.location.capture
        proxy_pass_request_headers off;
        #more_clear_input_headers Accept-Encoding;
        proxy_next_upstream error timeout;
        proxy_pass http://backend;
    }
}

upstream配置:http://nginx.org/cn/docs/http/ngx_http_upstream_module.html

  server:指定上游到的服务器, weight:权重,权重可以认为负载均衡的比例;  fail_timeout+max_fails:在指定时间内失败多少次认为服务器不可用,通过proxy_next_upstream来判断是否失败。

  check:ngx_http_upstream_check_module模块,上游服务器的健康检查,interval:发送心跳包的时间间隔,rise:连续成功rise次数则认为服务器up,fall:连续失败fall次则认为服务器down,timeout:上游服务器请求超时时间,type:心跳检测类型(比如此处使用tcp)更多配置请参考https://github.com/yaoweibin/nginx_upstream_check_modulehttp://tengine.taobao.org/document_cn/http_upstream_check_cn.html

  keepalive:用来支持upstream server http keepalive特性(需要上游服务器支持,比如tomcat)。默认的负载均衡算法是round-robin,还可以根据ip、url等做hash来做负载均衡。更多资料请参考官方文档。

 

tomcat keepalive配置: http://tomcat.apache.org/tomcat-7.0-doc/config/http.html

  maxKeepAliveRequests:默认100;

  keepAliveTimeout:默认等于connectionTimeout,默认60秒;

 

location proxy配置:http://nginx.org/cn/docs/http/ngx_http_proxy_module.html

  rewrite:将当前请求的url重写,如我们请求时是/backend/ad,则重写后是/ad。

  proxy_pass:将整个请求转发到上游服务器。

  proxy_next_upstream:什么情况认为当前upstream server失败,需要next upstream,默认是连接失败/超时,负载均衡参数。

  proxy_pass_request_headers:之前已经介绍过了,两个原因:1、假设上游服务器不需要请求头则没必要传输请求头;2、ngx.location.capture时防止gzip乱码(也可以使用more_clear_input_headers配置)。

  keepalive:keepalive_timeout:keepalive超时设置,keepalive_requests:长连接数量。此处的keepalive(别人访问该location时的长连接)和upstream keepalive(nginx与上游服务器的长连接)是不一样的;此处注意,如果您的服务是面向客户的,而且是单个动态内容就没必要使用长连接了。

 

vim /usr/servers/nginx/conf/nginx.conf

include /usr/chapter6/nginx_chapter6.conf;
#为了方便测试,注释掉example.conf
#include /usr/example/example.conf;

 

重启nginx 

/usr/servers/nginx/sbin/nginx -s reload      

 

访问如192.168.1.2/backend/ad?id=1即看到结果。可以kill掉一个tomcat,可以看到服务还是正常的。

 

vim /usr/chapter6/nginx_chapter6.conf 

    location ~ /backend/(.*) {
        internal;
        keepalive_timeout   30s;
        keepalive_requests  1000;
        #支持keep-alive
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        rewrite /backend(/.*) $1 break;
        proxy_pass_request_headers off;
        #more_clear_input_headers Accept-Encoding;
        proxy_next_upstream error timeout;
        proxy_pass http://backend;
    }

加上internal,表示只有内部使用该服务。 

 

Nginx+Lua逻辑开发

核心代码 

/usr/chapter6/ad.lua 

local redis = require("resty.redis")
local cjson = require("cjson")
local cjson_encode = cjson.encode
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_exit = ngx.exit
local ngx_print = ngx.print
local ngx_re_match = ngx.re.match
local ngx_var = ngx.var

local function close_redis(red)
    if not red then
        return
    end
    --释放连接(连接池实现)
    local pool_max_idle_time = 10000 --毫秒
    local pool_size = 100 --连接池大小
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)

    if not ok then
        ngx_log(ngx_ERR, "set redis keepalive error : ", err)
    end
end
local function read_redis(id)
    local red = redis:new()
    red:set_timeout(1000)
    local ip = "127.0.0.1"
    local port = 1111
    local ok, err = red:connect(ip, port)
    if not ok then
        ngx_log(ngx_ERR, "connect to redis error : ", err)
        return close_redis(red)
    end

    local resp, err = red:get(id)
    if not resp then
        ngx_log(ngx_ERR, "get redis content error : ", err)
        return close_redis(red)
    end
        --得到的数据为空处理
    if resp == ngx.null then
        resp = nil
    end
    close_redis(red)

    return resp
end

local function read_http(id)
    local resp = ngx.location.capture("/backend/ad", {
        method = ngx.HTTP_GET,
        args = {id = id}
    })

    if not resp then
        ngx_log(ngx_ERR, "request error :", err)
        return
    end

    if resp.status ~= 200 then
        ngx_log(ngx_ERR, "request error, status :", resp.status)
        return
    end

    return resp.body
end


--获取id
local id = ngx_var.id

--从redis获取
local content = read_redis(id)

--如果redis没有,回源到tomcat
if not content then
   ngx_log(ngx_ERR, "redis not found content, back to http, id : ", id)
    content = read_http(id)
end

--如果还没有返回404
if not content then
   ngx_log(ngx_ERR, "http not found content, id : ", id)
   return ngx_exit(404)
end

--输出内容
ngx.print("show_ad(")
ngx_print(cjson_encode({content = content}))
ngx.print(")")

将可能经常用的变量做成局部变量,如local ngx_print = ngx.print;使用jsonp方式输出,此处我们可以将请求url限定为/ad/id方式,这样的好处是1、可以尽可能早的识别无效请求;2、可以走nginx缓存/CDN缓存,缓存的key就是URL,而不带任何参数,防止那些通过加随机数穿透缓存;3、jsonp使用固定的回调函数show_ad(),或者限定几个固定的回调来减少缓存的版本。

 

vim /usr/chapter6/nginx_chapter6.conf 

    location ~ ^/ad/(\d+)$ {
        default_type 'text/html';
        charset utf-8;
        lua_code_cache on;
        set $id $1;
        content_by_lua_file /usr/chapter6/ad.lua;
    }

 

重启nginx

/usr/servers/nginx/sbin/nginx -s reload

 

访问如http://192.168.1.2/ad/1即可得到结果。而且注意观察日志,第一次访问时不命中Redis,回源到Tomcat;第二次请求时就会命中Redis了。

 

第一次访问时将看到/usr/servers/nginx/logs/error.log输出类似如下的内容,而第二次请求相同的url不再有如下内容:

redis not found content, back to http, id : 2

 

 

到此整个架构就介绍完了,此处可以直接不使用Tomcat,而是Lua直连Mysql做回源处理;另外本文只是介绍了大体架构,还有更多业务及运维上的细节需要在实际应用中根据自己的场景自己摸索。后续如使用LVS/HAProxy做负载均衡、使用CDN等可以查找资料学习。

 

16
1
分享到:
评论
19 楼 newboy2004 2017-12-12  
tomcat 配置 conf下Catalina下的localhost下的Root.xml 值为/usr/chapter6/webapp,访问没反应
18 楼 jinnianshilongnian 2016-05-04  
qa4862741 写道
请问是使用nginx自带的缓存好还是用redis来缓存页面好呢?

这个要根据实际情况来决定,记住redis单线程设计;然后再根据自己的场景选择
17 楼 qa4862741 2016-05-03  
请问是使用nginx自带的缓存好还是用redis来缓存页面好呢?
16 楼 jinnianshilongnian 2015-10-08  
zhanggok 写道
nginx,lua,tomcat是都部署到同一台机器上吗

1、有些是部署到一台
2、有些是单独不熟的
15 楼 zhanggok 2015-09-28  
nginx,lua,tomcat是都部署到同一台机器上吗
14 楼 zqb666kkk 2015-07-16  
这些架构写的很有意义  学习了
13 楼 gansuzxd 2015-06-04  
请问分表后如果产生多张表,要查询全部数据除了应用中Join多张表外,还有其他办法没?
12 楼 shenzhw 2015-05-26  
大侠,我问下,redis每次读取都重新连接是不是会很慢?
11 楼 showlike 2015-05-26  
/usr/local/mysql-proxy/bin/mysql-proxyd chapter6 start
启动MySQL-Proxy报如下异常:

2015-05-26 14:18:59: (message) Key file contains key 'daemon' which has a value that cannot be interpreted.
2015-05-26 14:18:59: (message) Key file contains key 'keepalive' which has a value that cannot be interpreted.
2015-05-26 14:18:59: (message) Initiating shutdown, requested from mysql-proxy-cli.c:318
2015-05-26 14:18:59: (message) shutting down normally, exit code is: 1
error: failed to start MySQL-Proxy of chapter6


    原因是因为我直接复制的 chapter6 配置内容,这样每行内容后都会存在空格,只要把空格出除重新启动即可
10 楼 showlike 2015-05-26  
j1078416970 写道
j1078416970 写道
安装ATLAS时候 ,make阶段

/usr/bin/ld: /usr/local/lib/liblua.a(lapi.o): relocation R_X86_64_32 against `luaO_nilobject_' can not be used when making a shared object; recompile with -fPIC
/usr/local/lib/liblua.a: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
make[3]: *** [libmysql-chassis.la] Error 1

求开涛大神指教



已解决,64位的server,安装lua-5.1.5,要调整一下Makefile:vi src/Makefile,在CFLAGS里加上-fPIC,否则会出错:


----细节解决步骤为:----------------------
1、cd Atlas-2.2.1/lua-5.1.5
2、先清除之前lua 的 library
#make clean
3、使用編輯軟體(vi)修改 src/Makefile
cd src
vim Makefile
找到第11行的地方加入 -fPIC 修改前後如下
修改前: CFLAGS= -O2 -Wall $(MYCFLAGS)
修改後:CFLAGS= -O2 -Wall -fPIC $(MYCFLAGS)
4、重新安装Lua
cd ../
make linux && make install
5、重新安装 Atlas
cd ../
make && make install


在ubuntu下#安装libevent依赖 
apt-get install libevent 会提示找不到
具体命令应该是
#安装libevent依赖 
apt-get install libevent-dev

以上为在安装Atlas所遇到问题,个人因环境而异,……——……
9 楼 j1078416970 2015-03-29  
j1078416970 写道
安装ATLAS时候 ,make阶段

/usr/bin/ld: /usr/local/lib/liblua.a(lapi.o): relocation R_X86_64_32 against `luaO_nilobject_' can not be used when making a shared object; recompile with -fPIC
/usr/local/lib/liblua.a: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
make[3]: *** [libmysql-chassis.la] Error 1

求开涛大神指教



已解决,64位的server,安装lua-5.1.5,要调整一下Makefile:vi src/Makefile,在CFLAGS里加上-fPIC,否则会出错:
8 楼 j1078416970 2015-03-29  
安装ATLAS时候 ,make阶段

/usr/bin/ld: /usr/local/lib/liblua.a(lapi.o): relocation R_X86_64_32 against `luaO_nilobject_' can not be used when making a shared object; recompile with -fPIC
/usr/local/lib/liblua.a: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
make[3]: *** [libmysql-chassis.la] Error 1

求开涛大神指教
7 楼 jinnianshilongnian 2015-03-06  
newboy2004 写道
jinnianshilongnian 写道
newboy2004 写道
/usr/chapter6
  redis_6660.conf
  redis_6661.conf
  nginx_chapter6.conf
  nutcracker.yml
  nutcracker.init
  webapp
WEB-INF
   lib
   classes
   web.xml&nbsp;


这块的代码 主要部署在哪的?tomcat
还是?

linux上,然后按照教程步骤配置



你这个结构是一个java工程结构吗,打包war包?

webapp里的是 其他的不是,只是放到一起了。
6 楼 newboy2004 2015-03-05  
jinnianshilongnian 写道
newboy2004 写道
/usr/chapter6
  redis_6660.conf
  redis_6661.conf
  nginx_chapter6.conf
  nutcracker.yml
  nutcracker.init
  webapp
WEB-INF
   lib
   classes
   web.xml&nbsp;


这块的代码 主要部署在哪的?tomcat
还是?

linux上,然后按照教程步骤配置



你这个结构是一个java工程结构吗,打包war包?
5 楼 jinnianshilongnian 2015-03-05  
newboy2004 写道
/usr/chapter6
  redis_6660.conf
  redis_6661.conf
  nginx_chapter6.conf
  nutcracker.yml
  nutcracker.init
  webapp
WEB-INF
   lib
   classes
   web.xml&nbsp;


这块的代码 主要部署在哪的?tomcat
还是?

linux上,然后按照教程步骤配置
4 楼 newboy2004 2015-03-05  
/usr/chapter6
  redis_6660.conf
  redis_6661.conf
  nginx_chapter6.conf
  nutcracker.yml
  nutcracker.init
  webapp
WEB-INF
   lib
   classes
   web.xml&nbsp;


这块的代码 主要部署在哪的?tomcat
还是?
3 楼 aa00aa00 2015-03-04  
文章不错,我得好好消化消化
2 楼 jinnianshilongnian 2015-03-03  
javac_xinyun 写道
开涛出品,必属精品。支持开涛!

谢谢
1 楼 javac_xinyun 2015-03-03  
开涛出品,必属精品。支持开涛!

相关推荐

    重磅!《Go Web编程实战派从入门到精通》随书源码开源啦,Go语言-Web-goWebActualCombat.zip

    《Go Web编程实战派从入门到精通》是一本专注于Go语言在Web开发领域的专著,其开源的随书源码库名为"Go语言-Web-goWebActualCombat",压缩包内的主要内容是goWebActualCombat-master目录,这代表了一个完整的Go语言...

    Python项目案例开发从入门到实战源代码第4章 调用百度API应用——小小翻译器

    在本项目案例中,我们将深入探讨如何使用Python编程语言调用百度API来开发一个简单的翻译工具,即"小小翻译器"。这个项目旨在帮助初学者...这是一个很好的起点,可以帮助你进一步探索更复杂的Web开发和数据分析任务。

    《Web前端设计基础——HTML5、CSS3、JavaScript》课后答案整理.pdf

    根据提供的文件内容,我们可以整理出以下Web前端设计基础的知识点: 1. HTML5、CSS3、JavaScript概述 - HTML5是最新版本的超文本标记语言,用于创建网页的标准标记语言。它具有更快的访问速度、更好的搜索引擎优化...

    李兴华 java_web开发实战经典 源码 完整版收集共享

    第1章 JAVA WEB开发简介 1.1、WEB发展历程 1.2、企业开发架构 1.3、JAVA EE架构 1.4、JAVA EE核心设计模式 1.5、Struts开发框架 1.6、本章摘要 1.7、开发实战讲解 第2章 HTML、JavaScript简介 2.1、服务器...

    李兴华Java Web开发实战经典(高清版) Part2

    第6章 JSP内置对象 6.1、JSP内置对象概览 6.2、四种属性范围 6.2.1、page属性范围(pageContext范围) 6.2.2、request属性范围 6.2.3、session属性范围 6.2.4、application属性范围 6.2.5、深入研究page...

    实战Web Service —— 使用Apache CXF开发Web服务的教程

    【标题】:“实战Web Service —— 使用Apache CXF开发Web服务的教程” 【内容详解】: Web服务是一种基于开放标准的、平台无关的接口技术,它允许不同系统间的应用程序进行交互。Apache CXF是一个开源的Java框架,...

    李兴华 Java Web 开发实战经典_带源码_高清pdf 带书签 上

    第1章 JAVA WEB开发简介 1.1、WEB发展历程 1.2、企业开发架构 1.3、JAVA EE架构 1.4、JAVA EE核心设计模式 1.5、Struts开发框架 1.6、本章摘要 1.7、开发实战讲解 第2章 HTML、JavaScript简介 2.1、服务器...

    大规模Web服务开发技术(PDF)-第2部分

    第1章 大规模Web服务的开发定位——掌握整体 第2章 大规模数据处理入门 第3章 操作系统的缓存和分布式 第4章 数据库的横向扩展策略 第5章 大规模数据处理“实践”入门 第6章 压缩编程 第7章 算法实用化 第8章 Hatena...

    李兴华Java Web开发实战经典.pdf (高清版) Part1

    第6章 JSP内置对象 6.1、JSP内置对象概览 6.2、四种属性范围 6.2.1、page属性范围(pageContext范围) 6.2.2、request属性范围 6.2.3、session属性范围 6.2.4、application属性范围 6.2.5、深入研究page...

    Node.js Web开发实战-(共14章)PPT.rar

    第6章 Web开发中的模板引擎.ppt 第7章 Node.js中的文件操作.ppt 第8章 认识express模块.ppt 第9章 MySQL数据库.ppt 第10章 Express框架.ppt 第11章 socket.io 模块.ppt 第12章 MongoDb数据库.ppt 第13章 综合项目...

    Python项目案例开发从入门到实战源代码第6章 爬虫应用——抓取百度图片

    在本项目案例中,我们将深入...此外,对于Python编程技巧和Web开发的理解也将得到提升,为后续的高级爬虫开发打下坚实的基础。在学习过程中,建议结合理论知识和实际操作,不断调试和改进代码,以深化理解和提高技能。

    GoWeb编程GoWeb编程

    Go语言,也称为Golang,是由Google开发的一种静态类型、编译型、并发型且具有垃圾回收功能的编程语言,特别适合构建高性能的Web服务。下面将详细讨论GoWeb编程中的关键知识点。 1. **Go语言基础**: - 语法特性:...

    项目实战——————网站

    在本项目实战中,我们构建了一个通用的WEB应用系统,主要涵盖了两个子系统:交易系统和管理系统。这两个系统是相互独立的,旨在提供一个全面的在线业务平台。以下是关于项目中涉及的技术栈及其具体应用的详细说明: ...

    bottle web开发 项目实战:任务管理系统 V1.0.rar

    【Bottle Web开发:项目实战——任务管理系统V1.0】 在Python的Web开发领域,Bottle是一个轻量级的框架,它以其简洁、高效和易于上手的特点受到许多开发者的喜爱。本项目实战——任务管理系统V1.0,旨在帮助你深入...

    云应用开发 ——Google App Engine & Google Web Toolkit入门指南

    #### 第6章 通信机制 ##### 6.1 RPC机制 远程过程调用 (RPC) 是一种常见的客户端-服务器通信模式,其中客户端应用程序可以调用远程服务器上的方法,就好像它们是在本地一样。GWT 提供了一种简单的方法来实现 RPC。...

    Java Web开发实战宝典 第15章 10-18 代码

    第15章涵盖了多个关键概念和实践,这些章节的代码示例10-18旨在帮助读者更好地理解并掌握Java Web应用的开发技巧。下面将详细解析这些章节的主要知识点: 1. **Servlet与JSP基础**:Java Web开发的核心之一是...

    MLDN+李兴华+Java+Web开发实战经典.part3.rar )

    第6章 JSP内置对象 6.1、JSP内置对象概览 6.2、四种属性范围 6.2.1、page属性范围(pageContext范围) 6.2.2、request属性范围 6.2.3、session属性范围 6.2.4、application属性范围 6.2.5、深入研究page...

Global site tag (gtag.js) - Google Analytics