最新京东面试题二(修正版4.0)

文章较长且干货满满,建议收藏!

Redis(内存管理 集群)

先看看redis是怎样管理内存的。redis为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存块的头部。如下图。

real_ptr ret_ptr

| |

V V

-------------------------------------------------------------------------

| size | memory block |

--------------------------------------------------------------------------

real_ptr是redis调用malloc后返回的指针。redis将内存块的大小size存入头部,size所占据的内存大小是已知的,为size_t类型的长度,然后返回ret_ptr。当需要释放内存的时候,ret_ptr被传给内存管理程序。通过ret_ptr,程序可以很容易的算出real_ptr的值,然后将real_ptr传给free释放内存。

redis会记录所有的内存分配情况。redis定义一个数组,这个数组的长度为ZMALLOC_MAX_ALLOC_STAT。数组的每一个元素代表当前程序所分配的内存块的个数,且内存块的大小为该元素的下标。在程序中,这个数组为zmalloc_allocations。zmalloc_allocations[16]代表已经分配的长度为16bytes的内存块的个数。zmalloc.c中有一个静态变量used_memory用来记录当前分配的内存总大小。

下面开始分析代码。

高并发的处理(自己的方案)

缓存、队列、集群(redis(缓存) nginx(动静分离) )

1、动静分离,静态资源请求与动态请求分离,项目中需要访问的图片、声音、js/css等静态资源需要有独立的存放位置,便于将来实现静态请求分离时直接剥离出来,比如nginx可以直接配置图片文件直接访问目录,而不需要经过tomcat。这样tomcat就可以专注处理动态请求,操作数据库数据处理之类的。静态请求代理服务器性能比tomcat高很多。

2、引入缓存,数据库缓存、页面缓存,这东西好用不复杂,搞明白什么地方适用最重要。简单的例子是频繁读取,不修改的地方最适用。也是后续集群做数据共享(比如session处理)的一个方式之一,集群环境下,经常会碰到数据共享问题。

3、如果将来数据量大,单一数据库成为瓶颈时,数据库的读写分离来了。数据库集群,读写分离,分表分区。

4、如果项目发展壮大了,已经过亿用户了,ok系统拆分来了。用户管理系统、订单系统,或者体育新闻系统、娱乐新闻系统等。保证一个系统当掉时不影响另一个,同时分担单系统压力。

spring的 代理模式(项目中怎么用) (手写代理)

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

1.静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

静态代理总结:

1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.

2.缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

/** 接口 */

public interface IUserDao {

void save();

}

/**接口实现 * 目标对象 */

public class UserDao implements IUserDao {

public void save() {

System.out.println("----已经保存数据!----");

}

}

/** * 代理对象,静态代理 */

public class UserDaoProxy implements IUserDao{

//接收保存目标对象

private IUserDao target;

public UserDaoProxy(IUserDao target){

this.target=target;

}

public void save() {

System.out.println("开始事务...");

target.save();//执行目标对象的方法

System.out.println("提交事务...");

}

}

/** * 测试类 */

public class App {

public static void main(String[] args) {

//目标对象

UserDao target = new UserDao();

//代理对象,把目标对象传给代理对象,建立代理关系

UserDaoProxy proxy = new UserDaoProxy(target);

proxy.save();//执行的是代理的方法

}

}

2.动态代理

动态代理有以下特点:

1.代理对象,不需要实现接口

2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

3.动态代理也叫做:JDK代理,接口代理

JDK中生成代理对象的API

代理类所在包:java.lang.reflect.Proxy

JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的

Class[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型

InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

总结:代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理



3.Cglib代理

上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.

Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)

Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法:

1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.

2.引入功能包后,就可以在内存中动态构建子类

3.代理的类不能为final,否则报错

4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

在Spring的AOP编程中:

如果加入容器的目标对象有实现接口,用JDK代理

如果目标对象没有实现接口,用Cglib代理

dubbo(好处-->(容错机制负载均衡))

(我的总结是2个作用点:负载均衡,服务中心。)

好处:软负载均衡及容错机制: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

可在内网替代F5等硬件负载均衡器,降低成本

Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,

只需用Spring加载Dubbo的配置即可

dubbo主要核心部件

Remoting:网络通信框架,实现了sync-over-async和request-response消息机制。

RPC:一个远程过程调用的抽象,支持负载均衡、容灾和集群功能。

Registry:服务目录框架用于服务的注册和服务事件发布和订阅。

Provider: 暴露服务的提供方。

Consumer:调用远程服务的服务消费方。

Registry: 服务注册中心和发现中心。

Monitor: 统计服务和调用次数,调用时间监控中心。(dubbo的控制台页面中可以显示)

Container:服务运行的容器。

调用关系:

0、服务器负责启动,加载,运行提供者(例如在tomcat容器中,启动dubbo服务端)。

1、提供者在启动时,向注册中心注册自己提供的服务。

2、消费者启动时,向注册中心订阅自己所需的服务。

3、注册中心返回提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

4、消费者,从远程接口列表中,调用远程接口,dubbo会基于负载均衡算法,选一台提供者进行调用,如果调用失败则选择另一台。

5、消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。(可以在dubbo的可视化界面看到)

dubbo的容错方案

当我们的系统中用到Dubbo的集群环境,因为各种原因在集群调用失败时,Dubbo提供了多种容错方案,缺省为failover重试。

Dubbo的集群容错在这里想说说他是因为我们实际的项目中出现了此类的问题,因为依赖的第三方项目出现异常,导致dubbo调用超时,此时使用的是默认的集群容错方式,而配置的reties='3',这样前段系统连续掉用了三次服务,结果可想而知.

先说一下各节点关系:

这里的Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息。

Directory代表多个Invoker,可以把它看成List,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。

Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。

Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。

LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。

集群容错模式:

Failover Cluster

失败自动切换,当出现失败,重试其它服务器。(缺省)

通常用于读操作,但重试会带来更长延迟。

可通过retries="2"来设置重试次数(不含第一次)。正是文章刚开始说的那种情况.

Failfast Cluster

快速失败,只发起一次调用,失败立即报错。

通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

失败安全,出现异常时,直接忽略。

通常用于写入审计日志等操作。

Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。

通常用于消息通知操作。

Forking Cluster

并行调用多个服务器,只要一个成功即返回。

通常用于实时性要求较高的读操作,但需要浪费更多服务资源。

可通过forks="2"来设置最大并行数。

Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)

通常用于通知所有提供者更新缓存或日志等本地资源信息。

重试次数配置如:(failover集群模式生效)

或:

或:

集群模式配置如:

或:

dubbo负载均衡策略:

在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调用。

RandomLoadBalance

随机,按权重设置随机概率。

在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin(绕版) LoadBalance

轮循,按公约后的权重设置轮循比率。

存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHashLoadBalance

一致性Hash,相同参数的请求总是发到同一提供者。

当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

Dubbo的集群容错和负载均衡同样也是Dubbo本身的高级特性.正如我们在说自定义扩展的时候一样,这两个特征同样也可以进行自定义扩展,用户可以根据自己实际的需求来扩展他们从而满足项目的实际需求.

参考出处: http://blog.csdn.net/lovesummerforever/article/details/48180957

http://blog.csdn.net/jnqqls/article/details/46702103

java8 的 新技术(流这块 单一 链式传递)

2. Java语言的新特性

2.1 Lambda表达式和函数式接口

Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> {

System.out.print( e );

System.out.print( e );

} );

Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:

String separator = ",";

Arrays.asList( "a", "b", "d" ).forEach(

( String e ) -> System.out.print( e + separator ) );

final String separator = ",";

Arrays.asList( "a", "b", "d" ).forEach(

( String e ) -> System.out.print( e + separator ) );

Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {

int result = e1.compareTo( e2 );

return result;

} );

Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

@FunctionalInterface

public interface Functional {

void method();

}

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface

public interface FunctionalDefaultMethods {

void method();

default void defaultMethod() {

}

}

Lambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档。

2.2 接口的默认方法和静态方法

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:

下面的代码片段整合了默认方法和静态方法的使用场景:

这段代码的输出结果如下:

Default implementation

Overridden implementation

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。

尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档。

2.3 方法引用

方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。

第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。

final Car car = Car.create( Car::new );

final List< Car > cars = Arrays.asList( car );

第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。

cars.forEach( Car::collide );

第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:

cars.forEach( Car::repair );

第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:

final Car police = Car.create( Car::new );

cars.forEach( police::follow );

运行上述例子,可以在控制台看到如下输出(Car实例可能不同):

Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

如果想了解和学习更详细的内容,可以参考官方文档

2.4 重复注解

自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。

在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:

正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。

另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:

filter1

filter2



进架构师群693845731,与大佬共同交流,免费领取架构师资料

关注微信公众号动力节点Java资源库,回复【 jgs 】即可免费领取系列学习资料、



你可能感兴趣的:(最新京东面试题二(修正版4.0))