现在出去找工作如果不会点分布式和微服务相关的内容,都不太好更面试官扯蛋。但这些架构也不是突然就出现的,而是经过不但演变才出现及流行起来的,本文就给大家来梳理下java项目架构的演变历程。
系统架构演化历程
单体架构
大型网站都是从小型网站发展而来的,网站架构也是一样,是从小型网站架构逐步演化而来的,小型网站最开始没有太多人访问,只需要一台服务器就绰绰有余了,这时的架构如下:应用程序、数据库、文件等所有的资源都在一台服务器上,通常服务器操作系统使用Linux、应用程序使用java或者其他语句,然后部署在Apache或者Nginx上。数据库使用MySQL,使用开源的技术实现,然后部署在一台廉价的服务器上就开始了网站的发展之路。
应用服务和数据服务分离
好景不长,随着公司业务的发展,一台服务逐渐满足不了需求,越来越多的用户访问导致性能越来越差,数据存储空间开始不足,这时我们需要将应用和数据分离,分离后开始使用三台服务器:应用服务器、文件服务器、数据库服务器。如图:
应用和数据分离后,不同特性的服务器承担着不同的服务角色,网站的并发处理能力和数据存储空间都得到了很大的改善,支持网站业务进一步发展,但是随着用户逐渐增多,数据库压力越来越大,访问延迟,进而影响整个网站的性能,此时需要进一步优化。
缓存的使用
网站访问有个著名的二八定律,即80%的业务集中访问在20%的数据上,如果我们将这一小部分的数据缓存在内存中,能够很好的减少数据库的访问压力,提高整个网站的数据访问速度。
缓存常用的组件可以是Redis,ehcache等。
集群的使用
缓存解决了数据库访问量比较大的问题,但是并不能解决随着业务增多造成的服务器并发压力大的问题,这时我们需要增加一台应用服务器来分担原来服务器的访问压力和存储压力。如图:
通过负载均衡调度服务器,可将来自用户的访问请求分发到应用服务器中的任何一台服务器中,这样多台服务器就分担了原来一台服务器的压力,我们只需要注意会话的一致性就可以了。
数据库读写分离
系统正常运行了一段时间后,虽然加的有缓存,使绝大多数的数据库操作可以不通过数据库就能完成,但是任然有一部分的操作(缓存访问不命中,缓存过期)和全部的写操作需要访问数据库,当用户达到一定规模后,数据库因为负载压力过大还是会成为系统的瓶颈,
这时主流的数据库都提供的有主从热备份功能,通过配置两台数据库实现主从关系,可以将一台数据库服务器的数据更新同步到另一台服务器上。可以利用这一功能来实现数据库读写分离。从而改善数据库的负载压力,如图:
mysql的读写分离可以通过自身自带的从主复制实现,Oracle的话可以通过阿里巴巴的mycat组件来实现。
反向代理和CDN加速
为了应付复杂的网络环境和不同地区用户的访问,通过CDN和反向代理加快用户访问的速度,同时减轻后端服务器的负载压力。CDN与反向代理的基本原理都是缓存。CDN部署在网络提供商的机房。用户请求到来的时候从距离自己最近的网络提供商机房获取数据,而反向代理则部署在网站的中心机房中,请求带来的时候先去反向代理服务器中查看请求资源,如果有则直接返回。如图:
使用CDN和反向代理的目的都是尽早返回数据给用户,一方面加快用户的访问速度,另一方面也减轻后端服务器的负载压力。
分布式文件和分布式数据库
任何强大的单一服务器都满足不了大型网站持续增长的业务需求。数据库经过读写分离后,从一台服务器拆分成两天服务器,但是随着业务的增长后面依然不能满足需求,这时我们需要使用分布式数据库,同时文件系统也一样,需要使用分布式文件系统。
分布式数据库是数据库拆分的最后的手段,只有在表单数据规模非常庞大的时候才使用,不到不得已时,我们更常用的手段是业务分库,将不同的业务数据部署在不同的物理服务器上。
NoSql和搜索引擎
随着业务越来越复杂,对数据存储和检索的需求也越来越复杂,这时一些NoSQL(Reids,HBase,mongodb)数据库技术和搜索引擎(Solr,Elasticsearch)的时候就显得很有必要。如下图:
NoSQL和搜索引擎对可伸缩的分布式特性具有更好的支持,应用服务器通过一个统一的数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。
业务拆分
当访问量达到一定规模的时候我们可以通过分而治之的手段将整个系统的业务分成不同的产品线,例如我们将系统的首页,商铺,订单,买家,卖家,支付,订单等拆分成不同的产品线。
具体到技术实现上,也可以根据产品线划分,将一个网站拆分成许多不同的应用,每个应用独立部署维护,应用之间通过RPC框架(dubbo,webService,httpClient…)建立连接,也可以通过消息队列实现异步分发处理。来构成一个完整的系统,如下图:
分布式服务
随着业务拆分越来越小,存储系统越来越庞大,应用系统的整体复杂度呈指数级增加,部署维护越来越困难,由于所有应用要和所有数据库系统连接,最终导致数据库连接资源不足,拒绝服务。1.当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
2.当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
3.接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
4.服务多了,沟通成本也开始上升,调某个服务失败该找谁?服务的参数都有什么约定?
5.一个服务有多个业务消费者,如何确保服务质量?
6.随着服务的不停升级,总有些意想不到的事发生,比如cache写错了导致内存溢出,故障不可避免,每次核心服务一挂,影响一大片,人心慌慌,如何控制故障的影响面?服务是否可以功能降级?或者资源劣化?
解决方案:公共的应用模块被提取出来,部署在分布式服务器上供应用服务器调用。也就是我们将的分布式服务或者微服务。
package com.bobo;
public interface UserService {
String sayHello(String name);
}
com.bobo
dubbo-commons
1.0-SNAPSHOT
org.springframework
spring-context
5.1.6.RELEASE
org.slf4j
slf4j-log4j12
1.7.25
com.alibaba
dubbo
2.5.3
spring
org.springframework
com.github.sgroschupf
zkclient
0.1
package com.bobo.service;
import com.bobo.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public String sayHello(String name) {
System.out.println("服务端执行了: " + name);
return "HELLO:" + name + "[msg]";
}
}
log4j.rootLogger=INFO,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
package com.bobo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppStart {
public static void main(String[] args) throws Exception{
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 挂起当前线程
Thread.currentThread().join();
}
}
com.bobo
dubbo-commons
1.0-SNAPSHOT
org.springframework
spring-context
5.1.6.RELEASE
org.slf4j
slf4j-log4j12
1.7.25
com.alibaba
dubbo
2.5.3
spring
org.springframework
com.github.sgroschupf
zkclient
0.1
package com.bobo;
import com.bobo.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppStart {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取接口的代理对象
UserService userService = (UserService) ac.getBean("userService");
System.out.println(userService.sayHello("aaa"));
}
}
dubbo.registry.address=zookeeper://192.168.100.120:2181?
backup=192.168.100.121:2181,192.168.100.122:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
dubbo.container=log4j,spring,registry,jetty
dubbo.application.name=simple-monitor
dubbo.application.owner=
# zookeeper的配置信息
dubbo.registry.address=zookeeper://192.168.100.120:2181?
backup=192.168.100.121:2181,192.168.100.122:2181
#dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=redis://127.0.0.1:6379
#dubbo.registry.address=dubbo://127.0.0.1:9090
dubbo.protocol.port=7070
dubbo.jetty.port=8080
dubbo.jetty.directory=${user.home}/monitor
dubbo.charts.directory=${dubbo.jetty.directory}/charts
dubbo.statistics.directory=${user.home}/monitor/statistics
dubbo.log4j.file=logs/dubbo-monitor-simple.log
dubbo.log4j.level=WARN
CREATE TABLE `users` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(30) DEFAULT NULL,
`userage` int(11) DEFAULT NULL,
PRIMARY KEY (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
dubbo-parent(POM)
|-- dubbo-pojo(jar)
|-- dubbo-mapper(jar)
|-- dubbo-user-provider(POM)
|-- dubbo-user-provider-interface(jar)
|-- dubbo-user-provider-service(jar)
|-- dubbo-user-consumer(POM)
|-- dubbo-user-consumer-web-service(jar)
|-- dubbo-user-consumer-web(war)
junit
junit
${junit.version}
org.slf4j
slf4j-log4j12
${slf4j.version}
org.mybatis
mybatis
${mybatis.version}
org.mybatis
mybatis-spring
${mybatis.spring.version}
mysql
mysql-connector-java
${mysql.version}
com.alibaba
druid
${druid.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-beans
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-aspects
${spring.version}
jstl
jstl
${jstl.version}
javax.servlet
servlet-api
${servlet-api.version}
provided
javax.servlet
jsp-api
${jsp-api.version}
provided
com.alibaba
dubbo
${dubbo-version}
com.101tec
zkclient
${zkClient-version}
src/main/java
**/*.xml
src/main/resources
**/*.xml
**/*.properties
org.apache.tomcat.maven
tomcat7-maven-plugin
${tomcat.version}
package com.bobo.pojo;
import java.io.Serializable;
public class User implements Serializable {
private Integer userid;
private String username;
private Integer userage;
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getUserage() {
return userage;
}
public void setUserage(Integer userage) {
this.userage = userage;
}
public User() {
}
public User(Integer userid, String username, Integer userage) {
this.userid = userid;
this.username = username;
this.userage = userage;
}
@Override
public String toString() {
return "User{" +
"userid=" + userid +
", username='" + username + '\'' +
", userage=" + userage +
'}';
}
}
/**
* UserMapper接口
*/
public interface UserMapper {
}
com.bobo
dubbo-pojo
1.0-SNAPSHOT
org.mybatis
mybatis
org.mybatis
mybatis-spring
mysql
mysql-connector-java
com.alibaba
druid
src/main/java
**/*.xml
com.bobo
dubbo-pojo
1.0-SNAPSHOT
com.bobo
dubbo-user-provider-interface
1.0-SNAPSHOT
com.bobo
dubbo-mapper
1.0-SNAPSHOT
org.springframework
spring-context
org.slf4j
slf4j-log4j12
com.alibaba
dubbo
2.5.3
spring
org.springframework
commons-logging
commons-logging
com.github.sgroschupf
zkclient
0.1
org.springframework
spring-jdbc
org.springframework
spring-aspects
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/shop?characterEncoding=utf-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
classpath:SqlMapperClient.xml
package com.bobo.dubbo;
import com.alibaba.dubbo.container.Main;
public class Start {
public static void main(String[] args) throws Exception{
// Dubbo提供的启动方法,默认会去classpath:/META-INF/spring/*.xml 加载
Main.main(args);
}
}
com.bobo
dubbo-user-provider-interface
1.0-SNAPSHOT
org.springframework
spring-context
org.springframework
spring-beans
dubbo-user-consumer
com.bobo
1.0-SNAPSHOT
4.0.0
dubbo-user-consumer-web
war
dubbo-user-consumer-web Maven Webapp
http://www.example.com
UTF-8
1.7
1.7
junit
junit
4.11
test
com.bobo
dubbo-user-consumer-web-service
1.0-SNAPSHOT
org.slf4j
slf4j-log4j12
org.springframework
spring-webmvc
javax.servlet
servlet-api
provided
jstl
jstl
javax.servlet
jsp-api
provided
com.alibaba
dubbo
2.5.3
spring
org.springframework
com.github.sgroschupf
zkclient
0.1
org.apache.tomcat.maven
tomcat7-maven-plugin
2.2
/
8082
dubbo-user-consumer-web
maven-clean-plugin
3.1.0
maven-resources-plugin
3.0.2
maven-compiler-plugin
3.8.0
maven-surefire-plugin
2.22.1
maven-war-plugin
3.2.2
maven-install-plugin
2.5.2
maven-deploy-plugin
2.8.2
contextConfigLocation
classpath:applicationContext-*.xml
org.springframework.web.context.ContextLoaderListener
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
springmvc
/
encoding
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
encoding
/*
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
package com.bobo.mapper;
import com.bobo.pojo.User;
/**
* UserMapper接口
*/
public interface UserMapper {
int insertUser(User user);
}
insert into users(username,userage)values(#{username},#{userage})
package com.bobo.dubbo.service;
import com.bobo.pojo.User;
public interface AddUserDubboService {
void addUser(User user);
}
com.bobo
dubbo-user-provider-interface
1.0-SNAPSHOT
com.bobo
dubbo-mapper
1.0-SNAPSHOT
org.springframework
spring-context
org.slf4j
slf4j-log4j12
com.alibaba
dubbo
2.5.3
spring
org.springframework
commons-logging
commons-logging
com.github.sgroschupf
zkclient
0.1
org.springframework
spring-jdbc
org.springframework
spring-aspects
package com.bobo.dubbo.service.impl;
import com.bobo.dubbo.service.AddUserDubboService;
import com.bobo.mapper.UserMapper;
import com.bobo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AddUserDubboServiceImpl implements AddUserDubboService {
@Autowired
private UserMapper userMapper;
@Override
public void addUser(User user) {
userMapper.insertUser(user);
}
}
public interface UserService {
int addUser(User user);
}
package com.bobo.service.impl;
import com.bobo.dubbo.service.AddUserDubboService;
import com.bobo.pojo.User;
import com.bobo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
/**
* 从Spring容器中获取 provider中的接口的代理对象
*/
@Autowired
public AddUserDubboService addUserDubboService;
@Override
public int addUser(User user) {
addUserDubboService.addUser(user);
return 1;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here