JAVA学习

常用学习资料总结

(1)系统中有哪些异常,你们系统是怎么去处理异常的?
异常有业务异常和运行异常。
业务异常:就是一些操作上的错误,比如用户密码错误,这一类错误应该尽可能给用户准确的提示信息。
运行异常:例如:服务间调用失败,内存溢出,数据转换异常,后台日志应该记录详细的错误堆栈信息,方便运维人员或者开发人员定位问题.

(2)mybatis框架如果实现一对多的关联关系。
两个实体类要实现一对多的关系需要,从表实体应该包含一个主表实体的对象引用。
一对多的关系映射,主表实体应该包含从表实体的集合引用。
Mapper映射文件的配置需要配置collection标签

public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

/**
 *  从表实体应该包含一个主表实体的对象引用
 */
private User user;

}

<resultMap id="userAccountMap" type="user">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="address" column="address"></result>
    <result property="sex" column="sex"></result>
    <result property="birthday" column="birthday"></result>

    <!--配置一对多user对象中account集合的映射-->
    <collection property="accounts" ofType="account">
        <id column="aid" property="id"></id>
        <result column="uid" property="uid"></result>
        <result column="money" property="money"></result>
    </collection>
</resultMap>

(3)Spring注解全面讲解:
1.非全注解开发,
第一组:这组注解关于对象的实例化创建,在功能上没有区别
@Component使用在类上用于实例化bean
@Controller使用在web层类上用于实例化bean
@Service使用的service层类上用户实例化bean
@Repository使用在dao层类上用于实例化bean
第二组:属性注入
@Autowired byType byName @Autowired注解默认按照类型装配,在默认情况下要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,
如果想使用名称装配可以结合使用@Qualifier注解
@Resource byName 默认安照名称进行装配,名称可以通过name属性进行指定 当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配
@Qulifier和@Autowired一起使用 byName
@Autowired+@Qualifier(value = “userDao”)这两个注解可以替换为@Resource(name=“userDao”),但是不建议使用;@Autowired+@Qualifier(value = “userDao”)还可以替换为单独的@Autowired,表示的是根据类型注入属性,当有多个相同类型
第三组:字符串注入
@Value:进行字符串的注入
@PropertySource :引入外部properties文件
@Bean:将方法返回值注入spring容器中
2.全注解开发
第一组:
@Scope : 决定对象是多例还是单例
@PostContruct:标注初始化方法
@PreDestory:标注销毁方法
第二组:
@Configuration :用于指定当前类是一个spring配置类,当创建容器的时候会从该类上加载注解
@ComponentScan: 用户指定spring在初始化容器的时候要扫描的包
@PropertySource: 用于加载.properties文件中的配置
@Import用于导入其他配置类
(4)Redis除了可以用来缓存,还可以用来做什么? 使用分布式锁的目的,无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作。
【1分布式锁】
分布式锁的本质就是在Redis里面占用一个“茅坑”,当别的进程进来也要占用时,发现已经有人蹲在那里了,只要放弃或者等待。
加锁:setnx(set if not exist)指令,先来先占,del可以释放锁。
异常场景:如果逻辑执行到中间异常,可能导致del指令没有调用,导致死锁。因此要给一个过期时间expire。
给过期时间异常场景:在setnx和expire之间服务器突然挂掉,可能因为机器断电或者人为杀死,导致expire得不到执行,也会死锁。

异常根本原因:setnx和expire是两条指令,而不是原子指令,这个时候注意不要使用Redis事务,因为expire是依赖于setnx的执行结果的,如果setnx没抢到锁,expire应该是不执行的,事务里没有判断。
Redis2.8版本加入了set指令扩展参数,可以使setnx和expire一起执行。例如:set lock: key true ex 5 nx ok…do Something critical… > del lock:codehole
【1超时问题】
注意:分布式锁不能解决超时问题。如果加锁和释放锁之间的逻辑执行时间太长,以至于超出了所得超时限制,就会出现问题。因为这个时候锁已经过期了,第二个线程重新拥有这把锁,但是紧接着第一个线程释放了锁,第三个线程就会在第二个线程逻辑执行之间拿到锁。为了避免:Redis分布式锁尽量不要用于较长时间的任务。如果真的偶尔出现了,数据出现的小波错乱可能需要人工介入解决。
有一个更加安全的方案,set指令的value参数设置一个随机数,释放锁的时候,先匹配随机数是否一致,然后再删除key。这个时候就要用到Lua脚本来处理了,Lua脚本可以保证连续多个指令的原子型执行。
【1可重入性】
可重入性是指线程持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。比如java中的ReentranLock就是可重入锁。Redis分布式要支持可重入锁,要对set进行包装,使用线程的ThreadLocal变量存储持有锁的计数。不推荐使用:加重了客户端复杂性,精确点还要考虑内存锁计数的过期时间。readLocal变量存储持有锁的计数。不推荐使用:加重了客户端复杂性,精确点还要考虑内存锁计数的过期时间。

【2延时队列】
为什么有的场景选择Redis作为延时队列,而不直接选择Rabbitmq和Kafka作为消息队列中间件,来给应用程序之间增加异步消息传递功能?
使用过MQ的都知道,在发消息之前要先创建Exchange,再创建Queue,还要讲Queue和Exchange通过某种规则绑定起来,发消息的时候指定routing-key。消费者消费消息之前也要进行一系列繁琐操作。
但是绝大多数情况下,对于一些消息队列只有一组消费者,使用Redis就可以非常轻松的搞定。但是Redis消息队列并不是专业的消息队列,没有那么多高级特征,没有ack保证,如果对消息可靠性有极致的追求,
他就不适用。
【2异步消息队列】
Redis和List(列表)数据结构常用来作为异步消息队列使用,使用rpush/lpush操作入队列,使用lpop,rpop来出队列。
队列空了怎么半?
客户端是通过队列的pop操作来获取消息,然后进行处理。处理完了再接着获取消息,再进行处理,如果循环往复,这便是作为队列消费者的客户端的生命周期。
但是队列空了,客户端会陷入pop的死循环,不停的pop,没有数据,浪费轮询空间,拉高了客户端CPU, Redis的慢查询也会增多。
通常我们使用sleep来解决这个问题,让线程睡一会儿,睡1s左右。不但客户端Cpu能将下俩,Redis的QPS也能降下来。
【2延迟队列】
上面睡眠会导致消息的延迟增大,有没有什么方法可以降低延迟呢?、
blpop/brpop blocking就是阻塞读,阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻过来。消息的延迟几乎为零,
用blpop/brpop替代前面的lpop/rpop,就完美解决了上面的问题
注意:使用blpop/brpop注意要捕获异常,防止Redis客户端成为闲置连接,闲置过久,服务器一般会主动断开连接,减少闲置资源占用。
【2锁冲突处理】 (1.直接抛出异常,通知用户稍后重试 2、sleep一会再重试 3、将请求转移延迟队列)
sleep: sleep会阻塞当前消息处理线程,会导致队列的后续消息处理出现延迟,如果碰撞的比较频繁或者队列里的消息比较多,sleep并不合适,。如果因为个别的死锁key导致加锁不成功,
线程会彻底堵死,导致后续消息永远得不到及时处理。
延时队列: 这种方式比较适合异步消息处理,将当前冲突的请求扔到另一个队列后处理以避开冲突。
【2延迟队列的实现】
延时队列可以通过Redis的zset(有序列表)来实现。我们将消息序列化成一个字符串作为zset的value,这个消息的到期时间作为score,然后用多个线程轮询zset获取到期的任务进行处理,
多个线程为了保障可用性,万一挂了还有另外一个线程可以继续处理。因为有多个线程,要考虑并发争抢任务,确保任务不被多次执行。
Redis的zrem方法是多线程争抢任务的关键,他的返回值决定了当前实例有没有抢到任务,因为loop方法可能会被多个线程调用。通过zrem决定唯一的属性。

【3】简单限流
除了控制流量,限流还有一个应用目的是用于控制用过户行为,避免垃圾请求,比如严格限定在规定时间内允许的次数。
如何实现限流:系统要限定用户的某个行为在指定时间只允许发生N次,如何使用redis的数据结构实现呢?
max_count
def is_action_allowed(user_id, action_key, period, max_count);
return true;
#调用这个接口,一分钟内只允许恢复5个帖子
can_reply = is_action_allowed(“laoqian”,“reply”, 60, 5);
if can_reply:
do_reply()
else
raise ActionThresholdOverflow()
限流存在一个滑动时间窗口,想想zset数据结构的score值,是不是可以通过score来圈出这个时间窗口,窗口之外的数据可以砍掉。
zset的value没有什么意义,保证唯一性即可,可以使用时间戳。
一个zset结构记录用户的行为历史,每个行为都会作为zset中的一个key保存下来,同一个用户的同一种行为用一个zset记录,为了节省内存,我们只需要保留时间窗口内的行为记录,
如果用户是冷用户,活动窗口内行为为空,则将zset从内存中移除,不再占用空间。
public class SimpleRateLimiter {
private Jedis jedis;
public SimpleRateLimiter(Jedis jedis) {
this.jedis = jedis;
}
public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
String key = String.format(“hist:%set”, userId, actionKey);
long nowTs = System.currentTimeMillis();
Pipeline pipe = jedis.pipelined();
pipe.multi();
pipe.zadd(key,nowTs, “”+nowTs);
pipe.zremrangeBScore(key,0,nowTs-period*1000);
Response count = pipe.zcard(key);
pipe.expire(key, period+1);
pipe.exec();
pipe.close();
return count.get()<=Maxcount();

}

}

(5) 使用zk作为分布式锁?(临时顺序节点+Watch监听机制)
优点:zookeeper分布式锁,能够优先解决分布式问题,不可重入问题(多次加锁),使用起来也比较简单。
缺点:使用zk作为分布式锁,性能并不太高,因为每次在创建锁和释放锁的过程中,都要动态创建,销毁临时节点来实现锁功能。大家知道,ZK中创建和删除节点只能通过Leader服务器来执行,
然后Leader服务器还需将数据同步到所有的Follower机器上,这样频繁的网络通信,性能短板非常突出。
总之:在高性能,高并发场景下,不建议使用zk分布式锁,但是在并发量不大的场景推荐使用zk的分布式锁。
基于Redis分布式锁:

(6)简单讲解一下Dubbo为什么选择Zk作为注册中心?运行机制和原理
Zookeeper是一个树形的目录服务,支持变更推送,适合作为Dubbox服务的注册中心,工作强度高,可用于生产环境。
Zk的数据模型简单,有一系列被称为ZNode的数据节点组成,与传统的磁盘文件系统不同的是,zk将全量数据存储在内存中,可谓是高性能,
而且支持集群,可谓是高可用,另外支持事件监听。这一点儿决定了zk非常适合作为注册中心(数据发布/订阅)
【注册中心工作流程】
1.服务提供方启动,会在zk上注册服务、在provider节点下创建一个子节点,并且写入自己的URL地址。
2.服务消费者在启动的时候,会像zk注册中心订阅自己的服务,其实就是读取所有zk上的子节点,并解析出来所有提供者的URL地址作为该服务地址列表。
同时还会再consumer下创建一个临时节点,并写入自己的URL地址。
3.消费者远程调用服务提供者,基于负载均衡算法,选择一个提供者进行调用。
4.增加提供者,也就是在provider下新建子节点,一旦服务提供方有变动,zk就会把最新的服务列表推送给消费者。
5.所有提供者在zk上创建的节点都是临时节点,利用临时节点的生命周期是和客户端会话相关的特性,因此一旦提供者所在的机器出现故障导致该提供者无法对外提供服务的时候,
该临时节点就会自动从zk上删除,同时,zk会把最新的服务列表推送给consumer者。
6.zk宕机之后,消费者每次调用服务提供方是不经过zk的,消费者只是从zk获取地址列表,所以zk宕机并不会影响消费者调用服务提供者,影响的是zk宕机之后如果提供者有变动,
增加或者减少,无法把最新的服务提供者地址 推送给消费者,所以消费者感知不到。

【优势】
当提供程序意外停止时,注册表服务器可以自动删除其信息。
注册表服务器重新启动后,可以自动恢复所有注册数据和订阅请求。
会话过期后,可以自动恢复所有注册数据和订阅请求。
【临时节点和持久节点】
在zk中,ZNode可以分为临时节点和持久节点,所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。
而临时节点就不一样了,他的生命周期和客户端会话绑定,一旦客户端失效,那么这个客户端所创建的所有临时节点都会被移除。

(5)JVM调优
JVM三大性能调优参数-Xms -Xmx -Xss
-Xss:规定了每个线程虚拟机栈(堆栈)的大小

-Xms:堆的初始值

-Xmx:堆能达到的最大值

-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值