Chen's Blog

技术债务


  • 首页

  • 关于

  • 归档

  • 搜索

并发编程实战学习笔记

发表于 2019-02-21 | 分类于 多线程 | | 阅读次数:

哈哈

阅读全文 »

线程池

发表于 2019-02-18 | | 阅读次数:

线程池的产生

  1. 创建、销毁线程伴随着系统开销、线程是可以重复适用的
  2. 线程并发数量过多,抢占系统资源从而导致阻塞
  3. 对线程要进行管理

Executor

具体实现类是ThreadPoolExecutor类

核心参数

corePoolSize

线程池的基本大小,即 在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超过这个数量的线程。在刚刚创建threadpoolexecutor的时候,线程不会立即启动,而是在有任务提交的时候才会启动,除非调用了prtestartCoreThread/prestartAllcoreThreads事先启动核心线程,没有任务执行的时候,线程池的大小不一定是corepoolSize

maximumPoolSize

线程池允许的最大线程数,线程池中的当前线程数目不会超过这个值,如果队列中任务已满,并且当前线程个数小于maximumpoolsize,那么会创建新的线程来执行任务。

keepAliveTime

如果一个线程处于空闲状态的时间超过该属性,就会因为超时而退出,如果这个线程是核心线程,线程退出取决于allowCoreThreadTimeOut

queueCapacity

阻塞队列 任务队列容量

blockingQueue

1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列,使用此阻塞队列时maximumPoolSizes就相当于无效
3、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。可以避免在处理可能具有内部依赖性的请求集时出现锁。

4、PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

rejectedExecutionHandler

当线程池发生两种情况,此时拒绝增加新的任务,将会采用线程池的饱和策略

  • 两种情况
    • 当线程已经达到maxpoolsize时,且队列已满,会拒绝新任务
    • 当线程池被调用shutdown时,会等待线程池里的任务执行完毕再shutdown
  • 饱和策略的类型
    • AbortPolicy 丢弃任务,抛运行时异常RejectedExecutionException 默认策略
    • CallerRunsPolicy 由调用者线程执行任务,如果调用者已关闭,则丢弃
    • DiscardPolicy 忽略
    • DiscardOldestPolicy 从队列中踢出最先进入队列的惹怒五
    • 实现RejectedExecutionHandler接口,可自定义处理器

执行顺序

1
2
3
4
5
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务

线程池的关闭

shutdown 与shutdownnow

常见的几种线程池

Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池

线程池监控

actuator 组件来做线程池的监控。

ThreadPool api

参数设置的方法

任务的类型

  • cpu密集型,
    • 较小的线程池,cpu核心数+1,cpu密集型任务会使cpu使用率很高,若开过多的线程数,只会增加上下文切换的次数,带来额外的开销
  • io密集型
    • cpu使用率不高,可以让cpu在等待io的时候去处理别的任务,充分利用cpu的时间
  • 混合型
    • 分别用不同的线程池去处理,两个任务的结束时间取决与后执行完的任务,适用于两个任务的执行时间相差不大

2N+1

N为cpu核数

根据几个值设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* 需要根据几个值来决定
- tasks :每秒的任务数,假设为500~1000
- taskcost:每个任务花费时间,假设为0.1s
- responsetime:系统允许容忍的最大响应时间,假设为1s
* 做几个计算
- corePoolSize = 每秒需要多少个线程处理?
* threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
* 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
- queueCapacity = (coreSizePool/taskcost)*responsetime
* 计算可得 queueCapacity = 80/0.1*1 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
* 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
- maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
* 计算可得 maxPoolSize = (1000-80)/10 = 92
* (最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
- rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
- keepAliveTime和allowCoreThreadTimeout采用默认通常能满足

源码分析

线程池的创建

添加任务

任务的获取

任务的执行

https://blog.csdn.net/tbdp6411/article/details/78443732

数据结构与算法

发表于 2019-01-02 | 分类于 算法 | | 阅读次数:

本文源于极客时间的数据结构和算法的读书笔记

数据结构是一组数据的存储结构,

算法是操作数据的一组方法

来历,特点,适合解决的问题,和实际的应用场景

阅读全文 »

跨域

发表于 2018-12-29 | | 阅读次数:

跨域

浏览器的同源策略

跨域资源共享cors

阅读全文 »

maven-setting

发表于 2018-11-16 | 分类于 maven | | 阅读次数:

maven settings 配置详解

阅读全文 »

基本日志

发表于 2018-11-06 | 分类于 基本日志 | | 阅读次数:

java中基本日志的使用,包括log4j,logback等

阅读全文 »

shiro工作流程

发表于 2018-11-01 | 分类于 shiro | | 阅读次数:

基本描述以及shiro是如何执行的,包含认证和授权流程

基本描述

  • Apache Shiro是Java的一个安全(权限)框架
  • 可以在JavaSE,javaEE环境
  • 功能点:认证,授权,加密、会话管理、web集成、缓存等

shiro功能点

应用程序角度

shiro外部架构

Shiro内部架构

1532848598409

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class UserRealm extends AuthorizingRealm {

private UserService userService;

public void setUserService(UserService userService) {
this.userService = userService;
}

// 从数据库中获取权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();

SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 从数据库中查询当前用户所拥有的角色
authorizationInfo.setRoles(userService.findRoles(username));
// 从数据库中查询当前用户所拥有的权限
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}

// 从数据库中获取认证信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException();//没找到帐号
}
if(Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); //帐号锁定
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), //用户名 principal
user.getPassword(), //密码 hashedCredentials
//注册的时候生成盐值 credentialsSalt
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realmName
);
return authenticationInfo;
}

}

工作流程

入口:DelegatingFilterProxy

web.xml中的shiro入口

1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

DelegatingFilterProxy 是Filter的代理,代理的是spring容器中的filter-name一样的bean,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/loginsuccess"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--
配置哪些页面需要受保护,以及访问这些页面需要的权限 过滤实现
anon 可以被匿名访问
authc 必须认证后才可以访问的页面
-->
<property name="filterChainDefinitions">
<value>
/loginsuccess = anon ===AnonymousFilter
/logout = logout
/user.jsp = roles[user]====RolesAuthorizationFilter
/** = authc ====FormAuthenticationFilter
</value>
</property>
</bean>

初始化:shiroFilter

1540385548793

ShiroFilterFactoryBean 实现了spring FactoryBean ,getObject获取实例

1
2
3
4
5
6
7
public Object getObject() throws Exception {
if (this.instance == null) {
this.instance = this.createInstance();
}

return this.instance;
}
1
2
3
4
5
6
7
8
9
10
11
12
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
//1、安全管理器
SecurityManager securityManager = this.getSecurityManager();
//2、过滤器链管理器
FilterChainManager manager = this.createFilterChainManager();
//3、基于路径匹配的过滤器链解析器
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//返回shirofilter对象到spring容器中
return new ShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager)securityManager, chainResolver);
}

property filterChainDefinitions 属性,会调用setFilterChainDefinitions

1
2
3
4
5
6
7
public void setFilterChainDefinitions(String definitions) {
Ini ini = new Ini();
ini.load(definitions);
//section key为url,value 为filter名称
Section section = ini.getSection("urls");
this.setFilterChainDefinitionMap(section);
}

SecurityManager

FilterChainManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected FilterChainManager createFilterChainManager() {
//创建DefaultFilterChainManager,添加默认的filters到manager
DefaultFilterChainManager manager = new DefaultFilterChainManager();
//设置默认Filter的基本属性
Map<String, Filter> defaultFilters = manager.getFilters();
for (Filter filter : defaultFilters.values()) {
//设置相关FilterURL的loginurl,successurl,unauthorizedUrl属性
applyGlobalPropertiesIfNecessary(filter);
}
//获取在spring配置文件中的配置的Filter,eg:logout
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
// 将配置的Filter添加至manager中,如果同名Filter已存在则覆盖默认Filter
manager.addFilter(name, filter, false);
}
}
//配置的FilterChainDefinition
Map<String, String> chains = this.getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
// 为配置的每一个URL匹配创建FilterChain定义,
// 这样当访问一个URL的时候,一旦该URL配置上则就知道该URL需要应用上哪些Filter
// 由于URL配置符会配置多个,所以以第一个匹配上的为准,所以越具体的匹配符应该配置在前面,越宽泛的匹配符配置在后面
manager.createChain(url, chainDefinition);
}
}
return manager;
}
DefaultFilterChainManager
1
2
3
4
5
public DefaultFilterChainManager() {
this.filters = new LinkedHashMap<String, Filter>();
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
addDefaultFilters(false);
}
1
2
3
4
5
protected void addDefaultFilters(boolean init) {
for (DefaultFilter defaultFilter : DefaultFilter.values()) {
addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}
}
DefaultFilter 过滤器

11个

1
2
3
4
5
6
7
8
9
10
11
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(facion ico.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
filterChains过滤器链

是一个linkedhashmap,key为配置的url,value为对应的过滤器

PathMatchingFilterChainResolver

创建了新的filterchainfilter,然后又被前面创建的覆盖了,有问题!!!

  1. 基于ant路径匹配方法匹配配置的url,

  2. pathMatchingFilterChainResolver设置创建的FilterChainManager对象,所以URL匹配上后可以获取该URL需要应用的FilterChain了。

filter执行—匹配filter

(错误)通过请求的url,获取要走的过滤链,或者说,所有请求都走一样的过滤链,过滤器中由判断自己是否支持该请求

(正解),所有的请求走一个过滤器org.springframework.web.filter.DelegatingFilterProxy#doFilter

1
2
3
4
5
org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter==执行被代理类doFilter
org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal
createSubject AbstractShiroFilter#createSubject== 创建subject
updateSessionLastAccessTime == 更新session最后访问时间
executeChain AbstractShiroFilter#executeChain ==获取过滤链,执行过滤方法
1
2
3
4
5
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain){
FilterChain chain = getExecutionChain(request, response, origChain);
//获取过滤链:如FormAuthenticationFilter
chain.doFilter(request, response);
}
1
2
eg:FormAuthenticationFilter#doFilter===父类OncePerRequestFilter的doFilter
OncePerRequestFilter#doFilter---doFilterInternal---AdviceFilter#doFilterInternal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AdviceFilter#doFilterInternal
preHandle
AdviceFilter#preHandle true
LogoutFilter#preHandle false
PathMatchingFilter#preHandle--isFilterChainContinued--onPreHandle
AnonymousFilter#onPreHandle true
NoSessionCreationFilter#onPreHandle true
PathMatchingFilter#onPreHandle true
AccessControlFilter#onPreHandle
isAccessAllowed
onAccessDenied
executeChain ==其他过滤器执行
postHandle ==没有实现类
cleanup
AdviceFilter#cleanup ==没实现
AuthenticatingFilter#cleanup
onAccessDenied
FormAuthenticationFilter#onAccessDenied
BasicHttpAuthenticationFilter#onAccessDenied

filter 执行顺序

https://www.cnblogs.com/ljdblog/p/6237683.html

https://www.cnblogs.com/q95265/p/6928081.html

filter执行—doFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain){
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
//已经执行过该过滤器
filterChain.doFilter(request, response);
} else if ( !isEnabled(request, response) ||shouldNotFilter(request) ) {
//该过滤器不适合该请求
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
//执行该过滤器
doFilterInternal(request, response, filterChain);
} finally {
// Once the request has finished, we're done and we don't
// need to mark as 'already filtered' any more.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}

shiro通过一系列url匹配符配置URL应用上的Filter,然后在Filter中完成相应的任务。核心逻辑-doFilterInternal

OncePerRequestFilter:保证每个filter都执行一次

​ –AbstractShiroFilter

​ –AdviceFilter

AbstractShiroFilter

springfactorybean返回的bean核心就是这个类,封装为shirofilter

构造方法

1
2
3
4
5
6
7
8
9
10
public void init() throws Exception {
WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());
//SecurityManager
setSecurityManager(env.getWebSecurityManager());
//FilterChainResolver
FilterChainResolver resolver = env.getFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
final Subject subject = createSubject(request, response);
//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
}
createSubject

每次调用都会创建subject

executeChain
1
2
3
4
5
6
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
//获取当前url匹配的过滤器链
FilterChain chain = getExecutionChain(request, response, origChain);
//执行过滤器链中过滤器
chain.doFilter(request, response);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
//获取过滤器链解析器,即创建的PathMatchingFilterChainResolver对象
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
}
// 调用其getChain方法,根据URL匹配相应的过滤器链
FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
log.trace("Resolved a configured FilterChain for the current request.");
chain = resolved;
} else {
log.trace("No FilterChain configured for the current request. Using the default.");
}
return chain;
}

AdviceFilter

这个类很多方法实现了spring中的aop特点,prehandle前置通知,posthandle后置通知,异常不执行,afterCompletion (最终通知,一定会执行),可以根据需求覆写这几个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain){
Exception exception = null;
try {
//前置通知,判断该过滤链是否支持该请求
boolean continueChain = preHandle(request, response);
if (continueChain) {
//执行过滤器链
executeChain(request, response, chain);
}
//后置通知
postHandle(request, response);
} catch (Exception e) {
exception = e;
} finally {
//最终通知
cleanup(request, response, exception);
}
}

eg:

  • preHandle
  1. 根据配置,访问URL:”/authenticated.jsp”时,会匹配上authc(FormAuthenticationFilter)
  2. FormAuthenticationFilter继承自PathMatchingFilter,所以返回true ,而logoutfilter会返回false,啥也没有返回给页面,页面显示空白
1
2
3
4
5
6
7
8
9
10
11
12
13
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
for (String path : this.appliedPaths.keySet()) {
// If the path does match, then pass on to the subclass implementation for specific checks
//(first match 'wins'):
if (pathsMatch(path, request)) {
log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path);
Object config = this.appliedPaths.get(path);
return isFilterChainContinued(request, response, path, config);
}
}
//no path matched, allow the request to go through:
return true;
}
1
2
3
4
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
String path, Object pathConfig) throws Exception {
return onPreHandle(request, response, pathConfig);
}

org.apache.shiro.web.filter.AccessControlFilter#onPreHandle

1
2
3
4
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
//如果isAccessAllowed方法返回false,则会执行onAccessDenied方法
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可

org.apache.shiro.web.filter.authc.AuthenticatingFilter#isAccessAllowed

1
2
3
4
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}

org.apache.shiro.web.filter.authc.AuthenticationFilter#isAccessAllowed

1
2
3
4
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//是否为登陆请求
if (isLoginRequest(request, response)) {
//是否是POST请求
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}
//执行跳转到登陆界面login.jsp
saveRequestAndRedirectToLogin(request, response);
return false;
}
}

login.jsp也会被拦截,执行到这里然后访问login.jsp,

再然后post请求这个url,就会执行executeLogin

org.apache.shiro.web.filter.authc.AuthenticatingFilter#executeLogin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
"must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);
//重定向到上次访问的url,过滤链不再执行
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
//过滤链继续执行,返回到登陆界面
return onLoginFailure(token, e, request, response);
}
}

认证流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
//securityManager 登录
Subject subject = securityManager.login(this, token);

PrincipalCollection principals;

String host = null;

if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}

if (principals == null || principals.isEmpty()) {
String msg = "Principals returned from securityManager.login( token ) returned a null or " +
"empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken) token).getHost();
}
if (host != null) {
this.host = host;
}
Session session = subject.getSession(false);
if (session != null) {
this.session = decorate(session);
} else {
this.session = null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
//关键逻辑
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
//认证成功,重新创建subject
Subject loggedIn = createSubject(token, info, subject);
//处理rememberme逻辑
onSuccessfulLogin(token, info, loggedIn);

return loggedIn;
}

org.apache.shiro.mgt.AuthenticatingSecurityManager#authenticate

1
2
3
public AuthenticationInfo authenticate(AuthenticationToken token) {
return this.authenticator.authenticate(token);
}

org.apache.shiro.authc.AbstractAuthenticator#authenticate

​ org.apache.shiro.authc.pam.ModularRealmAuthenticator#authenticate

模板方法模式

1
2
3
4
5
6
7
8
9
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
//多realm下的认证策略默认实现为AtLeastOneSuccessfulStrategy
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
//关键实现方法
AuthenticationInfo info = realm.getAuthenticationInfo(token);
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//覆盖实现核心认证信息
info = doGetAuthenticationInfo(token);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
}
if (info != null) {
assertCredentialsMatch(token, info);
}
return info;
}

eg:自定义realmA实现AuthenticatingRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ShiroRealmA extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("===>ShiroRealmA<=====" + JSON.toJSONString(token));
/**
* 1. 吧AuthenticationToken转为usernamepasswordtoken
* 2. 获取用户名与密码
* 3. 调用数据库的方法
* 4. 根据数据库用户情况抛出异常或构建AuthenticationInfo
*/
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
// 数据库获取的密码,,前端加密 密码加密
//盐值一般是唯一值,可以是用户名
ByteSource salt = ByteSource.Util.bytes(username);
Object credentials = "123";
Object credentialsMD5 = new SimpleHash("MD5", credentials, salt, 2);

if ("zs".equals(username)) {
return new SimpleAuthenticationInfo(username,credentialsMD5, salt, this.getName());
} else {
throw new AuthenticationException();
}
}
}

授权流程

perms(PermissionsAuthorizationFilter.class) :url是否有权限

roles(RolesAuthorizationFilter.class), :url是否有该角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
//访问需要的权限
String[] perms = (String[]) mappedValue;
//subject判断是否有权限
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
if (!subject.isPermitted(perms[0])) {
isPermitted = false;
}
} else {
if (!subject.isPermittedAll(perms)) {
isPermitted = false;
}
}
}
return isPermitted;
}

ModularRealmAuthorizer.isPermitted

1
2
3
4
5
6
7
8
9
10
11
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
// 调用Realm的isPermitted方法
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
1
2
3
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
1
2
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
return this.authorizer.isPermitted(principals, permissionString);
1
2
3
4
5
6
7
8
9
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}

public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {

AuthorizationInfo info = null;
//缓存中获取授权信息
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
}
if (info == null) {
// 继承覆盖实现获取授权信息
info = doGetAuthorizationInfo(principals);
if (info != null && cache != null) {
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}

return info;
}

ModularRealmAuthorizer.isPermittedAll

AuthorizationFilter#onAccessDenied

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = getSubject(request, response);
//未登陆,跳转到登陆界面
if (subject.getPrincipal() == null) {
saveRequestAndRedirectToLogin(request, response);
} else {
//登陆未授权、返回未授权界面或者401状态码
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
return false;
}

FormAuthenticationFilter

Onceperrequestfilter#doFilter

adviceFilter#doFilterInternal

AdviceFilter#preHandle true

AuthenticatingFilter#cleanup

FormAuthenticationFilter#onAccessDenied

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//是否为登陆请求
if (isLoginRequest(request, response)) {
//是否是post请求
if (isLoginSubmission(request, response)) {
return executeLogin(request, response);
} else {
return true;
}
} else {
saveRequestAndRedirectToLogin(request, response);
return false;
}
}

org.apache.shiro.web.filter.authc.AuthenticatingFilter#executeLogin

1
2
3
4
5
6
7
8
9
10
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response);
try {
Subject subject = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}

go to 认证流程

PermissionsAuthorizationFilter

Onceperrequestfilter#doFilter

adviceFilter#doFilterInternal

PathMatchingFilter#preHandle

AccessControlFilter#onPreHandle

go to 授权流程

​ PermissionsAuthorizationFilter#isAccessAllowed

​ AuthorizationFilter#onAccessDenied

RolesAuthorizationFilter

与PermissionsAuthorizationFilter类似

UserFilter

是登录页面或者必须登录后获得principalCollection,才能通过

1
2
3
4
5
6
7
8
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {  
if (isLoginRequest(request, response)) {
return true;
} else {
Subject subject = getSubject(request, response);
return subject.getPrincipal() != null;
}
}
1
2
3
4
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
saveRequestAndRedirectToLogin(request, response);
return false;
}

参考文献

https://blog.csdn.net/xtayfjpk/article/details/53729135

http://suichangkele.iteye.com/blog/2277023

servlet-filter-listener-interceptor

发表于 2018-10-25 | 分类于 web | | 阅读次数:

概念

servlet

  • 是一种运行再服务端的java应用程序,具有独立与平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求和服务端响应的中间层
  • 创建并返回给客户端完整的html页面

filter

  • 过滤器,是一个可以复用的代码片段、可以用来转换http请求、响应和头信息。不像servlet,它不能产生一个请求或响应、只能修改某一资源的请求、或者修改某一资源的响应
  • 能够在一个请求到达servlet之前预处理用户请求,也可以在离开servlet时处理http响应
  • url传来后,检查之后,可保持原来的流程继续向下执行,被下一个filter处理,servlet接收后,不会向下传递。

listenter

  • 监听器、通过listener可以监听web服务器中的某一个执行动作、并根据其要求做出相应的响应。再application\session\request三个对象创建消亡或者添加修改属性时自动执行代码的功能组件
  • servlet、filter时针对url之类的,而listener是针对对象的操作,在这些对象变化的时候做一些操作

interceptor

  • 面向切面编程中,就是再你的servcie或者一个方法前后,比如动态代理就是拦截器的简单实现,只能对controller请求进行拦截
  • 类似与框架带的filter
    • 不在web.xml中配置,框架配置文件中
    • 由action自己指定用哪个interceptor来接受处理,并且访问action上下文,过滤器可以对几乎所有的请求起作用
    • 拦截器是基于java 反射的,过滤器是基于函数回调的
    • 拦截器不依赖与serlvet容器,过滤器依赖于servlet容器

生命周期

servlet

它被装入web服务器的内存,并在web服务器终止或者重新装入servlet时结束。servlet一但装入web服务器,一般就不会在web服务器内存中删除、直到web服务器关闭或重新结束。

  1. 装入:启动服务器时加载servlet的实例
  2. 初始化:web服务器启动时或者接受到请求时,初始化工作init()方法负责执行完成
  3. 调用:从第一次到以后的多次访问,都只调用doget()或dopost方法
  4. 销毁:停止服务器时调用destory()方法,销毁实例

filter

Filter接口方法有init().doFilter(),dodestory()

  1. 启动服务器时加载过滤器实例,并调用init()方法来初始化实例
  2. 每一次请求时都只调用方法doFilter()进行处理,调用下一过滤器chain.doFilter
  3. 停止服务器时调用destory()方法,销毁实例

listener

interceptor

  • struts 的拦截器在加载xml后,初始化相应拦截器,当action请求来时调用intercept方法,服务器停止销毁interceptor
  • prehandle(action执行之前),posthandle(方法执行之后,不一定会执行),aftercompletion(最终执行)

顺序

web.xml的加载顺序context-param->listener->filter->servlet

执行顺序:

filter1————————–web.xml中的配置顺序

​ ->interceptor1(prehandle)–框架中的拦截器

​ ->servlet1

​ ->interceptor1(posthandle,aftercompletion)

->filter1

1540519294642

springmvc拦截器

应用举例

filter

在过滤器中修改字符编码,在过滤器中修改request的参数(链路跟踪)

interceptor

日志,在方法调用前后打印出字符串

参考文献

https://blog.csdn.net/sundenskyqq/article/details/8549932

https://blog.csdn.net/tanga842428/article/details/52175683

https://blog.csdn.net/zxd1435513775/article/details/80556034

https://www.cnblogs.com/jzb-blog/p/6717349.html

性能监控

发表于 2018-10-25 | 分类于 性能监控 | | 阅读次数:

为什么要性能监控

了解系统的运行情况,有计划的改善项目的运行,提供系统的性能,保证系统稳定性等

监控哪些

系统层面

last min avg max

cpu

  1. cpu jumps
    1. context switchs per second:线程的切换,切换过多会导致cpu忙于切换,影响吞吐率
    2. interrupts per second
  2. cpu load cpu使用队列的长度的统计信息
    1. processor load (1 min average per core)
    2. processor load (5 min average per core)
    3. processor load (15 min average per core)
  3. cpu utilization 利用率
    1. cpu idle time
    2. cpu user time
    3. cpu system time
    4. cpu iowait time
    5. cpu nice time
    6. cpu interrupt time
    7. cpu steal time

memory内存

  1. memory usage
    1. avaulable memory

磁盘

  1. disk space usage
    1. total disk space in
    2. free disk space on
  2. disk space /opt

network网络

  1. network traffic on eth0
    1. incoming network traffic on eth0
    2. outgoing network traffic on eth0

软件层面

数据库

tomcat

jvm

应用方面

应用类型

  • IO相关:Io相关的应用通常用来处理大量数据,需要大量内存和存储,频繁IO操作读写操作,而对cpu的要求比较少,大部分时候cpu再等待磁盘eg:数据库服务器、文件服务器
  • CPU相关:CPU相关大的应用需要大量cpu资源,比如高并发的web/mail服务器、图像、视频处理、科学计算等

方法论

压测

jmeter

线程就是并发数、吞吐量就是QPS

监控工具

工具 简单介绍
top 查看进程活动状态以及一些系统状况
vmstat 查看系统状态、硬件和系统信息等
iostat 查看cpu负载、硬盘状态
sar 综合工具、查看系统状态
mpstat 查看多处理器状况
netstat 查看网络状况
iptraf 实时网络状况检测
tcpdump 抓取网络数据包、详细分析
mpstat 查看多处理器状况
tcptrace 数据包分析工具
netperf 网络带宽工具
dstat 综合工具、综合了vmstat\iostat\ifstat\netstat等

参考文献

https://blog.csdn.net/tianlesoftware/article/details/6198780

https://blog.csdn.net/marising/article/details/5182771

缓存初始

发表于 2018-10-19 | 分类于 缓存 | | 阅读次数:

什么是缓存

临时存放数据(读多写少)的地方,介于外部请求和真实数据之间;

什么情况下要缓存

数据的类型,业务的特点:读多写少,在正常工作的前提下响应时间尽可能短。

缓存的类型

根据缓存位置的不同的不同分为硬件缓存,客户端缓存,服务端缓存

  • 硬件缓存:硬盘缓存和cpu缓存,提高硬盘和cpu之间的暂存器
  • 客户端缓存:会把用户之前浏览的东西存在本地,在下次访问是,如果本地的缓存有请求的内容,就直接取,不再向服务器请求
  • 服务端缓存:如果每次客户端请求都要链接一次数据库,当用户请求多的时候,负载过大,这时把一些经常请求的数据存放在内存中,当有请求时直接返回,不经过数据库,这样就可以减轻数据库负担

进程缓存or分布式缓存or多级缓存

失效策略(缓存算法)

如果缓存满了,而当前请求又没有命中缓存,那么会按照一种策略,把缓存中的某一种旧资源剔除,而把新的资源加入缓存,这些决定应该剔除哪个旧资源的策略就是失效策略

  • 成本:如果缓存对象有不同的存储成本,应该把那些难以获得的对象保存下来
  • 容量:如果缓存对象有不同的大小,应该把那些大的缓存清除,让更多的小缓存对象进来
  • 时间:一些缓存设定有过期时间,应该在到时间之后将他们失效

常见失效策略

simple time-based


通过绝对的时间周期删除缓存对象,对于新增的对象,保存设定的时间后,将其失效,原理简单,效果不好

extended time-based expiration


通过相对时间去失效缓存对象,对于新增的缓存对象,会保存特定的时间,比如每5分钟,每天12点

sliding time-based expiration


计算最后访问这个缓存的时间算起,最后一次访问对象1小时后

FIFO:First in First Out


判断被存储的时间,最早缓存数据优先被剔除

LRU:Least Recently Used


最近最少,使用一个链表来保存。新数据插入链表头部,命中后,将数据移到链表头部,满了后,尾部数据丢弃,有个热点数据有段时间没访问,会导致这个热点数据淘汰

LFU:Least Frequently Used


最不经常使用原则,在一段时间内,数据命中 次数最少,优先被剔除,利用额外的空间记录每个数据的使用频率,选出频率最低的进行淘汰

对比与分析

失效策略出发点有两个,基于时间(FIFO)、基于命中次数(LRU,MRU,2Q),最常使用的时FIFO,LRU,LFU

不同的访问模型导致命中率变化较大,实际应用中需要根据业务的需求和对数据的访问请求进行选择,并不是命中率越高越好,考虑点包括:命中率,复杂度?,代价(时间复杂度、空间复杂度)等,如何衡量。。。

更新缓存的方法

定时失效

mq通知

缓存问题

缓存穿透

查询一个不存在的数据,首先,缓存命不中,数据库查不到,这样的请求每次都会请求数据库,流量大的时候,可能数据库就挂了。

解决办法:

  • 过滤器:布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截
  • 设定指定值:如果一个查询返回的数据为空,依然要把空结果进行缓存,缓存的值设定位一个指定值,同时设置它的过期时间很短,最长不超过5分钟

缓存雪崩

在某一时刻,大量缓存同时失效,请求全部到数据库,导致数据压力过大

解决办法:

  • 加锁或者队列的方式保证缓存的单线程来写,把失效时间分开
  • 将数据失效时间均匀分布在时间轴上,在原有的失效时间基础上增加一个随机值

缓存并发

如果一个缓存如果失效,可能出现多个进程同时查询DB,

解决办法:

对缓存查询加锁,如果key不存在,就加锁,然后查DB写入缓存,然后解锁

大厂经验

爱奇艺

  1. 数据同步加Redis:依赖于Redis,一旦redis挂了,整个缓存系统不可用
  2. javamap加redis:进程内缓存作为一级缓存,redis作为二级缓存,进程内缓存无法做到实时更新,当不需要淘汰机制,可以使用map,问题:
    1. 锁竞争严重
    2. 不支持过期时间
    3. 不支持自动刷新
  3. guava cache加redis:设置写后刷新时间,进行刷新,解决了一直不更新的问题,但是依然没有解决实时刷新
  4. 外部缓存异步刷新:利用redis作为消息队列通知机制,通知其他应用进行刷新

参考文献

  • https://juejin.im/post/5b7593496fb9a009b62904fa#comment
  • https://juejin.im/post/5b849878e51d4538c77a974a
  • http://twei.site/2017/07/15/%E7%BC%93%E5%AD%98%E9%82%A3%E4%BA%9B%E4%BA%8B-%E4%BB%80%E4%B9%88%E6%98%AF%E7%BC%93%E5%AD%98%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8%E7%BC%93%E5%AD%98/
123
Chen

Chen

27 日志
14 分类
36 标签
© 2019 Chen
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4