架构题一
你的掌心温度不高,却足以温暖我。 --zhu
1、说说架构模式
1,分层。
2,分割。
分层是对网站进行横向的切分,分割是对网站纵向的切分。将网站按照不同业务分割成小应用,可以有效控制网站的复杂程度。
3,分布式
在大型网站中,分层和分割后主要是为了让网站能够便于分布式部署,也就是不同的模块部署到不同的服务器上。常用分布式方案有:
3.1 分布式应用和服务
3.2 分布式静态资源
3.3 分布式数据和存储
3.4 分布式计算
3.5 分布式配置,分布式锁,分布式文件系统等。
4,集群
分布式方案只是将不同模块或者服务独立部署到服务器上,但通常还是单台服务器。集群则是将同一个模块或服务同时部署到多台服务器上,通过负载均衡设备对外提供服务。
5,缓存
常用缓存有:CDN,反向代理,本地缓存,分布式缓存
6,异步
异步一般通过队列的方式来实现。在单一服务器中,可以通过多线程共享内存队列实现异步。在分布式系统中,可以通过分布式消息队列实现。
异步好处:提高系统可用性。加快网站响应速度。消除并发访问高峰。
7,冗余
冗余的目的实现高可用性。通过使用集群实现。即使再小的服务,也要部署到至少两台服务器上。
数据库冗余有冷备份和热备份两种。
冗余还包括在异地建立灾备数据中心。
8,自动化
自动化包括自动化代码管理,自动化部署,自动化测试,自动化安全检查等。
9,安全
安全主要通过密码加手机验证码的方式实现。
2、架构5大要素
1.高性能架构
2.高可用架构
3.伸缩性架构
4.可扩展架构
5.安全架构
3、什么是集群,什么是分布式
集群:一个系统部署到多台服务器,多台服务器完成业务处理,业务请求接收,每一台服务器都能独立完成业务计算;每个服务器都是独立个体;多台服务器集合起来,通常需要做负载均衡。
分布式:多台服务器完成业务处理,请求收到后,需要多个服务器合作完成。比如一个业务处理分五个环节,A处理1,B处理2......一个业务请求,五台服务器合作完成。
4、对Redis的理解
Redis是一种基于键值对的NoSql数据库(非关系型数据库);是一个key-value存储系统
Redis特点:高性能 可靠性
高性能:Redis将所有数据都存储在内存中,所有读写性特别高
可靠性:Redis将内存中的数据利用RDB和ADF的形式保存到硬盘中,这样可以避免发生断点或机器故障时内存数据丢失。
功能应用:
1.数据缓存功能,减少对数据库的访问压力
2.消息队列功能(轻量级):Redis提供了发布订阅功能和阻塞队列功能
3.计数器-应用保存用户凭证
比如计算浏览数,如果每次操作都要做数据库的对应更新操作,将会给数据库性能带来极大挑战
缓存:优化网站性能,首页(不常变的信息)
存储:单点登录,购物车
计数器:登录次数限制,incr
时效性:验证码expire
订单号:数字
redis有哪些应用场景?
1.缓存数据服务器 --SSO单点登录
2.应对高速读写的场景 --秒杀高可用
3.分布式锁 --秒杀数据一致性
3.数据共享 --库存数据
5、除了Redis,还有哪些NoSql
Memcache/MongoDb
6、对消息队列的理解
传统应用程序,如果需要向另一个应用程序发送信息,只需要向其发出请求即可。虽然简单直接,但如果应用程序2挂了,应用程序1可能会因为服务异常,无法继续提供服务。如果两个程序中间插入一个消息服务,用于节省消息和发送消息,这样2个程序之间依赖关系就解耦了,不会因为一个程序出问题,无法继续服务。
1.程序解耦:应用程序1和2进行交互时,不会因为一方服务中断而导致服务停止。
2。异步处理:应用程序1只管把消息发送到消息中间件,应用程序2只需要从消息中间件接收消息进行处理即可。同时,基于异步处理特性,在商品秒杀活动,引入消息队列之后,当客户端请求量很大时,可用有效进行流量削峰。如果没有中间件缓冲,一下大量请求进入,很可能造成系统瘫痪。
弊端
1.系统可用性降低:消息丢失或者消息队列服务挂掉等情况。
解决方案:搭建消息服务集群,具体技术实现上可以是主从架构或者分布式架构,一台消息队列服务器挂了,也不会影响消息队列无法提供服务。
2.系统复杂度提高:需要保证消息没有被重复消费、处理消息没有正确处理等问题。
解决方案:有多种,比如接收消息后,可以写入数据库,如果没有正确处理,可以走人工处理,或者失败时将消息重新入队等待下一次消费。
7、如何理解数据库读写分离
数据库读写分离是一种数据库架构策略,用于提高数据库的可用性和性能。在这种架构中,数据库的读操作(查询)和写操作(更新、插入、删除)被分离到不同的数据库服务器上。
提高性能:通过将读操作和写操作分离,可以减少单个数据库服务器的负载,从而提高整体性能。
扩展性:读写分离允许系统通过增加读服务器来水平扩展,以应对更多的查询请求。
可用性:在主数据库(写操作)出现故障时,可以通过将读请求重定向到其他读服务器来保持服务的可用性。
负载均衡:读写分离可以通过负载均衡器来实现,它可以将读请求均匀地分配到多个读服务器上。
数据一致性:读写分离可能会引入数据一致性的问题,因为读服务器可能不会立即反映写服务器上的最新数据。这通常通过数据复制技术来解决,如主从复制。
主从复制:在读写分离的架构中,通常有一个主数据库(Master)负责处理写操作,以及一个或多个从数据库(Slave)负责处理读操作。主数据库的数据会异步或同步复制到从数据库。
为什么要读写分离,分表分库?
单表数据量限制,当单表数据多到一定程度数据库性能会显著下降。数据多了,对数据库读写就很多,分库能减少单台数据库压力。
有些系统,通过主键进行散列分库分表,主键是唯一的获取消息的主要途径。比如:京东订单、财付通交易记录等,通过数订单号、交易号查询订单、交易。
也有系统,比如用户信息,每个用户都有特定userId,与Id对应的还有用户能看到的个人信息,此时就是通过userId散列分库。
8、如果系统出现性能问题,如何排查
通过浏览器访问,定位性能最差的请求,找出是代码层面的问题还是数据库层面的问题,根据不同环节解决性能问题。
9、列出常见的缓存方式,简述优缺点
1.内存缓存:
优点:访问速度快,数据存储在内存中
缺点:数据容易丢失,服务器重启缓存数据就丢失;受限于物理内存大小
2.分布式缓存
优点:可扩展性强,支持多台服务器共享缓存数据
缺点:需要维护缓存系统的高可用性和一致性,网络延迟可能影响访问速度
3.本地缓存(应用程序缓存)
优点:实现简单,访问速度快
缺点:不适合多实例部署,每个实例需要独立缓存,导致数据不一致
4.数据库缓存
优点:数据库管理系统自动处理缓存,无需额外配置。
缺点:缓存大小和有效性由数据库控制,可能不如专用缓存系统灵活。
5.浏览器缓存
优点:减少服务器负载,加速页面加载速度。
缺点:缓存控制依赖于HTTP头部设置,不当配置可能导致内容更新不及时。
6.文件系统缓存:
优点:简单易用,不需要特殊的缓存服务器。
缺点:访问速度慢于内存缓存,文件系统I/O可能成为瓶颈。
10.如何理解通信加密解密
加密是将原始数据(明文)转换为一种无法直接理解的形式(密文)的过程,以防止未授权访问。
目的:
保护数据隐私:防止数据在传输过程中被截获和阅读。
确保数据完整性:确保数据在传输过程中未被篡改。
类型:
对称加密:使用相同的密钥进行加密和解密。
非对称加密:使用一对密钥,即公钥和私钥;公钥用于加密,私钥用于解密。
流程:
发送方使用加密算法和密钥将明文转换为密文。
发送方将密文通过不安全的通道发送给接收方。
应用场景:
保护网络通信,如HTTPS、VPN等。
保护存储的数据,如数据库加密、文件加密等。
11、PDB是什么东西,在调试中应该放在哪里
PDB是用于保存调试和项目状态信息的文件,在debug时产生pdb文件,调试时应该放在对应应用程序集相同目录。
12、C#中Params是什么含义
params关键字是一个属性,它可以被应用于方法的参数。当一个方法参数被params修饰时,它允许调用者传递一个可变数量的参数给这个方法。这些参数在方法内部被看作是一个数组。
用途:
可变参数列表:使用params可以让方法接受任意数量的参数,从零个到多个。
简化调用:调用者不需要创建数组或集合来传递参数,可以直接传递一系列参数。
方法重载:params关键字常用于方法重载,允许方法通过不同的参数数量来覆盖。
示例:
public class Example
{
// 使用params关键字定义方法,可以接受任意数量的int类型参数
public void PrintNumbers(params int[] numbers)
{
foreach (var number in numbers)
{
Console.WriteLine(number);
}
}
}
class Program
{
static void Main(string[] args)
{
var example = new Example();
// 调用方法,传递不同数量的参数
example.PrintNumbers(); // 不传递任何参数
example.PrintNumbers(1); // 传递一个参数
example.PrintNumbers(1, 2, 3); // 传递多个参数
}
}
params修饰的参数必须是方法的最后一个参数,params修饰的参数类型必须是一个数组类型。
13、说说对http301,302,303,304,400,405,415状态码的认识
301 Moved Permanently(永久重定向)
含义:请求的资源已被永久移动到新的URL,服务器会返回新的URL地址。
用途:用于网址重构时,将旧的URL指向新的URL,搜索引擎和用户代理(如浏览器)会更新他们的记录。
影响:对SEO(搜索引擎优化)有利,因为链接权重会被转移到新的URL。
302 Found(临时重定向)
含义:请求的资源临时被移动到另一个URL,但资源还是会在原来的URL下。
用途:用于临时内容变更,搜索引擎在索引时会回到原始URL。
影响:对SEO的影响较小,因为搜索引擎知道这不是永久性的变更。
303 See Other(查看其他位置)
含义:请求的资源存在另一个URL下,客户端应使用GET方法获取资源。
用途:通常用于POST请求后,服务器希望用户查看一个已更新的页面,如表单提交后。
影响:改变用户代理的行为,从POST变为GET请求。
304 Not Modified(未修改)
含义:自从上次请求后,请求的资源未被修改,可以使用缓存的版本。
用途:用于缓存控制,减少不必要的数据传输,提高效率。
影响:对用户体验和性能有利,减少了重复加载相同内容的需要。
400 Bad Request(错误请求)
含义:服务器无法理解请求,因为请求语法错误或请求无效。
用途:告知客户端其请求存在问题,需要修改后重新发送。
影响:需要用户或客户端开发者检查请求并进行修正。
405 Method Not Allowed(不允许的方法)
含义:请求行中指定的方法不被允许,如尝试在只支持GET的资源上使用POST。
用途:告知客户端其使用的HTTP方法不被服务器支持。
影响:需要客户端改变请求方法或寻找其他途径。
415 Unsupported Media Type(不支持的媒体类型)
含义:请求的媒体类型不被服务器支持,如上传了一个服务器不识别的文件格式。
用途:告知客户端其提交的数据格式不被接受。
影响:需要客户端提交服务器支持的媒体类型。
14、什么是异步编程
异步编程是一种编程范式,它允许程序在等待特定操作完成时继续执行其他任务,而不是被阻塞。这种范式在处理I/O密集型或高延迟操作(如网络请求、文件读写、数据库操作等)时特别有用,因为它可以提高应用程序的响应性和吞吐量。
在C#中,异步编程通常使用async和await关键字来实现。async关键字用于声明一个方法为异步方法,而await关键字用于等待一个异步操作完成,同时释放当前线程以执行其他任务。
public async Task DoAsyncWorkAsync()
{
// 模拟异步操作
var result = await SomeAsyncOperation();
// 处理结果
Console.WriteLine(result);
}
private async Task<string> SomeAsyncOperation()
{
// 模拟异步延迟
await Task.Delay(1000);
return "Operation completed";
}
在JavaScript中,可以使用Promise和async/await语法来实现异步编程:
async function doAsyncWork() {
try {
const result = await someAsyncOperation();
console.log(result);
} catch (error) {
console.error(error);
}
}
function someAsyncOperation() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Operation completed");
}, 1000);
});
}
15、架构模式,设计模式,代码模式区别是什么
架构模式关注于系统的高层结构,即组件如何相互协作以及系统的组织方式。它们提供了一种在宏观层面上组织软件的方法,通常用于解决系统级别的问题。如:分层架构,微服务架构,事件驱动架构
设计模式关注于解决在软件设计过程中遇到的常见问题。它们是经过验证的、可重用的解决方案,适用于特定的设计问题,通常在代码层面上实现。如:单例模式,工厂模式,观察者模式
代码模式(有时也称为惯用法或最佳实践)是编写代码时的具体实践和技术,它们通常更加具体和详细,关注于提高代码的可读性、可维护性和性能。如:DRY(避免代码重复,通过抽象和模块化减少冗余),KISS(保持代码简单直接,避免不必要的复杂性),SOLID原则(包括单一职责、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则)
16、软件架构的目标是什么
1.满足需求:
架构应该满足系统的功能需求和非功能需求,如性能、可靠性、可用性等。
2.可维护性:
设计易于理解和维护的架构,使未来的修改和扩展更加容易。
3.可扩展性:
架构应允许系统在不影响现有功能的情况下添加新功能或处理增加的负载。
4.灵活性:
架构应提供足够的灵活性,以适应未来的变化和不确定性。
5.模块化:
通过将系统分解为独立的、功能明确的模块来简化开发和维护。
6.解耦:
减少系统组件之间的依赖,提高组件的独立性和可重用性。
7.可重用性:
设计可重用的组件和模块,以减少开发工作量并提高开发效率。
8.性能优化:
确保架构支持高性能,包括快速响应和高吞吐量。
9.安全性:
架构应包含安全措施,以保护系统免受未授权访问和攻击。
17、迪米特法则
迪米特法则(Law of Demeter, LoD)又称为最少知识原则(Principle of Least Knowledge),是一种软件工程中的设计原则,用于指导如何构建具有松散耦合的模块化系统。核心思想是:一个对象应该对其他对象有尽可能少的了解。
1.只与直接朋友通信;2.不要和“陌生人”说话;3.通过参数传递对象.
迪米特法则的目的是减少对象之间的耦合度,使得每个对象只需要了解与它直接相关的对象。这有助于提高代码的模块化、可读性和可维护性。
示例:
假设有三个类:A, B, C。根据迪米特法则,类A可以直接调用类B的方法,如果B是A的成员或者通过参数传递给A。但是,如果A想要调用C的方法,而C不是A的直接成员或参数,A应该通过B来间接访问C的方法(假设B有C的引用),而不是直接访问。
18、里氏替换原则
核心观点:如果一个系统是使用基类对象设计的,那么它应该能够无缝地使用其子类对象,而不需要对系统造成任何破坏或需要进行额外的适应。
// 基类
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Some generic animal sound");
}
}
// 子类,遵循里氏替换原则
public class Dog : Animal
{
public override void MakeSound() // 子类可以有自己的实现
{
Console.WriteLine("Bark");
}
}
// 另一个子类
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow");
}
}
public class Program
{
public static void Main(string[] args)
{
// 使用基类引用指向子类对象,演示里氏替换原则
Animal myDog = new Dog();
Animal myCat = new Cat();
// 调用基类的方法,实际执行子类的实现
myDog.MakeSound(); // 输出: Bark
myCat.MakeSound(); // 输出: Meow
// 里氏替换原则保证了这种用法是安全的
List<Animal> animals = new List<Animal> { myDog, myCat };
foreach (var animal in animals)
{
animal.MakeSound(); // 根据对象的实际类型输出不同的声音
}
}
}
增强代码的可重用性:通过确保子类可以替换基类,可以提高代码的可重用性。
减少代码错误:遵循LSP可以减少因继承带来的错误,因为子类不会改变基类的行为。
提高代码的可维护性:当子类遵循LSP时,维护和扩展现有代码变得更加容易。
促进开闭原则:LSP与开闭原则(对扩展开放,对修改封闭)
19、依赖倒转IOC原则
其核心观点是:
高层模块不应该依赖于低层模块,两者都应该依赖于抽象(接口或抽象类)。
抽象不应该依赖于细节,细节应该依赖于抽象。
通过依赖注入(DI)实现:
// 定义一个接口,作为高层模块和低层模块之间的契约
public interface IDependency
{
void PerformTask();
}
// 低层模块实现接口的具体实现
public class ConcreteDependency : IDependency
{
public void PerformTask()
{
Console.WriteLine("Performing task...");
}
}
// 高层模块依赖于接口,而不是具体的实现
public class HighLevelModule
{
private readonly IDependency _dependency;
// 通过构造函数注入依赖
public HighLevelModule(IDependency dependency)
{
_dependency = dependency;
}
public void DoSomethingImportant()
{
_dependency.PerformTask();
// 高层模块的业务逻辑
}
}
public class Program
{
public static void Main(string[] args)
{
// 创建具体依赖的实例
IDependency dependency = new ConcreteDependency();
// 将依赖注入高层模块
HighLevelModule module = new HighLevelModule(dependency);
// 使用高层模块
module.DoSomethingImportant();
}
}
示例中:
IDependency 是一个接口,定义了高层模块和低层模块之间的契约。
ConcreteDependency 是 IDependency 的具体实现。
HighLevelModule 是一个高层模块,它通过构造函数接收一个 IDependency 的实例。这样,高层模块不依赖于 ConcreteDependency 的具体实现,而是依赖于抽象的 IDependency。
在 Main 方法中,我们创建了 ConcreteDependency 的实例,并将其注入到 HighLevelModule 中,展示了依赖注入(DI)的过程。
20、为什么说基于SOAP的服务是重量级服务,Rest是轻量级
SOAP服务由于其规范性、复杂性和对特定协议的依赖,通常被认为是重量级服务。而REST服务由于其简单性、灵活性和对HTTP协议的直接使用,被认为是轻量级服务。Restful Web服务是基于REST和HTTP协议的轻量级Web服务。
1.协议复杂性:
SOAP:基于XML,需要遵循严格的规范和格式。SOAP消息通常包含大量的XML标签,这使得消息体变得冗长和复杂。
REST:通常使用JSON或XML作为数据格式,结构更简单、更易于阅读和编写。
2.性能:
SOAP:由于XML的解析和生成比JSON更耗时,SOAP服务在性能上可能不如REST服务高效。
REST:JSON的解析和生成通常更快,使得REST服务在性能上更优。
3.数据交换格式:
SOAP:只使用XML作为数据交换格式,这限制了它的灵活性。
REST:可以使用JSON、XML等多种格式,适应不同的客户端需求。
4.协议支持:
SOAP:需要特定的协议支持,如WSDL(Web Services Description Language)来描述服务和SOAP消息的格式。
REST:使用标准的HTTP方法(GET、POST、PUT、DELETE等),不需要额外的描述语言。
21、Session有什么重大BUG,微软提出了什么方法解决
针对IIS中的进程回收机制可能导致的Session丢失问题,可以通过使用State Server或SQL Server数据库存储Session来解决,尽管这些方法可能速度较慢且无法捕获Session的END事件。
22、.NET内存分配机制是什么
.NET内存分配机制主要依赖于垃圾回收器(Garbage Collector, GC)。垃圾回收器是一个自动化的内存管理机制,负责自动跟踪对象的使用情况,并在确定对象不再被使用时释放它们占用的内存。
23、如何通过.NET性能
1.代码优化:
避免重复的计算,使用有效的算法和数据结构。
减少不必要的对象创建,重用对象实例。
使用值类型而不是引用类型,当适用时。
2.使用多线程和并行编程:
利用.NET的System.Threading、Task Parallel Library (TPL)或async和await来实现并发执行。
3.内存管理:
减少内存分配,尤其是在高频调用的方法中。
使用对象池来重用对象,避免频繁的垃圾回收。
4.优化数据库访问:
使用参数化查询防止SQL注入,并提高查询性能。
优化数据库索引,减少查询时间。
批量处理数据库操作,减少往返次数。
5.使用缓存:
利用内存缓存或分布式缓存(如Redis)来存储频繁访问的数据。
6.异步编程:
使用异步编程模型来避免阻塞I/O操作,提高应用程序的响应性。
7.资源池:
对于数据库连接、文件句柄等资源,使用连接池或其他形式的资源池。
8.配置优化:
调整垃圾回收的配置,例如调整堆大小或禁用某些GC事件。
9.减少网络延迟:
优化数据传输,使用压缩技术减少网络负载。
10.优化UI线程:
确保UI线程不被阻塞,使用异步或后台任务来处理耗时操作。
11.使用依赖注入:
通过依赖注入简化组件之间的耦合,提高测试性和可维护性。
12.使用性能计数器:
利用性能计数器来监控应用程序的运行时性能。
13.优化配置文件:
合理配置web.config或app.config,避免不必要的配置项。
14.减少反射的使用:
反射虽然强大,但可能导致性能问题,应谨慎使用。
15.使用.NET Core或.NET 5+:
利用.NET Core或.NET 5及更高版本的性能改进和优化。
24、网站优化:网站运行慢,如何定位,如何解决
前端:
1.减少http请求,每次发送http请求都会消耗一定时间。
2.使用js缓存,浏览器缓存,直接从缓存读取数据,不请求服务器。
3.使用压缩后的css和js,避免css和js重复使用,减少js循环次数。
后端:
1.优化SQL,避免使用*查询,使用索引,避免sp中出现大量逻辑事务,减少in或and和or的查询使用。
2.使用memcache缓存,减少数据库访问。
3.减少代码层级接口,避免循环嵌套,优化算法。
4.读写分离,负载均衡,面向接口编程,降低耦合性。
25.IEnumerable和IQueryable的区别
定义:
IEnumerable 是一个泛型接口,它定义了可以被枚举的集合的成员。它允许你使用 foreach 循环来遍历集合中的元素。
IQueryable 是一个泛型接口,它扩展了 IEnumerable 并添加了对查询的扩展方法的支持,特别是对延迟执行的支持。
延迟执行:
IEnumerable 通常与立即执行相关联。当调用 IEnumerable 接口的方法时,如 ToList() 或 ToArray(),操作会立即执行并返回结果。
IQueryable 支持延迟执行。这意味着查询直到真正需要结果时才会被执行,这在处理大型数据集时非常有用。
数据源:
IEnumerable 可以用于任何类型的数据源,包括内存集合(如列表或数组)。
IQueryable 通常用于数据源,如关系型数据库,它允许查询在数据库层面进行优化和执行。
性能:
使用 IEnumerable 时,所有的数据转换和查询操作都在内存中完成,这可能会导致性能问题,特别是当处理大量数据时。
IQueryable 可以将查询转换为数据库可以理解的形式,利用数据库的优化器来提高查询性能。
使用场景:
当你只需要对内存中的数据进行操作,并且不需要延迟执行或数据库优化时,使用 IEnumerable。
当你需要构建可以在数据库上执行的查询,并且希望利用数据库的优化能力时,使用 IQueryable。
总结:IEnumerable 更适合内存中的集合操作,而 IQueryable 更适合与数据库交互,支持延迟执行和数据库级别的查询优化。
26.lock为什么要锁定一个参数,可不可锁定一个值类型?这个参数有什么要求?
lock 通常用于锁定引用类型,以确保对共享资源的线程安全访问。虽然也可以锁定值类型,但这通常不是推荐的做法,因为它可能导致混淆和错误。lock 关键字通常用于实现线程同步,确保在多线程环境中对共享资源的访问是安全的。
参数要求:
不变性:锁对象在整个应用程序的生命周期内应该是不变的,以避免在锁定期间发生意外的行为。
可见性:锁对象应该是所有线程都能访问到的,以确保所有线程都能看到锁的状态。
单一性:锁对象应该是唯一的,以避免不同的线程锁定不同的对象,导致同步失败。

浙公网安备 33010602011771号