在线商城Java项目(第三阶段)-服务端(Tomcat集群与Redis分布式)
可在电脑上阅读,以获取最佳阅读效果,谢谢!
已支持在线查看PDF电子书版本
			
项目开始 20180122
结束时间 20180305

Git代码地址:https://gitee.com/jiangjiesheng/mmall/tree/v2.0
接口文档:https://gitee.com/jiangjiesheng/mmall/wikis/Home

点击在线阅读PDF电子书版本(无需下载)

目录:
	一、二期项目初始化
	二、Lombok
	三、Maven环境隔离
	四、Tomcat集群
	五、Redis
	六、Redis+Cookie+Jackson+Filter实现单点登录(SSO-Single Sign On)-集群调试(多进程debug)
	七、Redis分布式
	八、Spring Session实现单点登录(对业务没有入侵)
	九、Spring MVC全局异常
	十、Spring MVC拦截器
	十一、Spring MVC RESTful原理及改造
	十二、Spring Schedule定时关单
	十三、Redis分布式锁核心代码
	十四、Redisson框架项目集成
	十五、Spring Schedule+Redisson构建分布式任务调度
	十六、云服务器部署


一、三期项目初始化
git branch
git branch -r
创建基于1.0分支的2.0分支版本
git checkout -b v2.0 origin/v1.0
git push origin 	HEAD -u

修改项目pom.xml
1.0-SNAPSHOT 改成   2.0-SNAPSHOT

二、Lombok
1、介绍及优点
	提高编码效率
	使代码更简洁
	消除冗长代码
	避免修改字段名称时忘记修改方法名
	注:IDE必须要支持Lombok
2、原理
	Source File -> Parse -> AST(抽象语法树) -> 
	Annotation Processing -> Lombok Annotation Processor -> AST ->
	Lombok Annotation Handler -> Modified AST
3、官网
	https://projectlombok.org/
4、引入
	http://mvnrepository.com/artifact/org.projectlombok/lombok-maven/1.16.18.0
	
	<dependency>
	    <groupId>org.projectlombok
	    <artifactId>lombok-maven
	    <version>1.16.18.0
	    <type>pom
	</dependency
5、安装Lombok插件
	IDEA:
	File -> Settings -> Plugins -> Browse repositories -> 搜索 Lombok -> install -> 重启
	eclipse:
	下载Lombok.jar,双击jar包(https://projectlombok.org/download)
6、常见注解
	@Data --> 包含了getter 和 setter
	@Getter
	@Getter(AccessLever.PROTECTED)
	@Setter
	@Setter(AccessLever.PROTECTED)
	@NoArgsConstructor
	@AllArgsConstructor
	@ToString
	@ToString(exclude="columm")
	@EqualsAndHashCode --> 自动重写
	@EqualsAndHashCode(exclude="columm")
	@Slf4j --> 有小写的log ,本项目中使用logback日志框架,所以使用@Slf4j
	@Log4j(当项目使用log4j日志框架时使用)
	
	@ToString(exclude=“column”)
	@ToString(exclude={"column1","colnum2"})
	@ToString(of="column")
	@ToString(of={"column","colnum2"})
	@EqualsAndHashCode同上
	
	@Data 包含了@Getter @Setter @ToString @EqualsAndHashCode

7、反编译
	Java Decompiler
	http://jd.benow.ca

	选择JD-GUI 
8、在类需要序列化、反序列化时详细控制字段时 例如:Jackson json序列化

9、获取maven配置
	在https://projectlombok.org/setup/maven 中复制出来

10、改造pojo实体
	A、改造Cart实体
	 	注释掉除了字段之外的其他所有代码,添加
	@Data //包含getter、setter
	@NoArgsConstructor
	@AllArgsConstructor
	@ToString

B、改造Category实体
	@Data //包含getter、setter
	@NoArgsConstructor
	@AllArgsConstructor
	@EqualsAndHashCode(of = "id")
	//@EqualsAndHashCode(of = {"id","name"}) 多个字段时
	@ToString(exclude = "updateTime")

C 使用Lombok引入日志 CategoryServiceImpl.java 添加 @Slf4j 注解,
	并将private Logger logger 对象改成log

	打包测试
	mvn clean package -Dmaven.test.skip=true

三、Maven环境隔离
	本地开发环境(local)
	开发环境(dev)
	测试环境(beta)
	线上环境(prod)
	配置好pom.xml
	A、在</build>节点中添加
	 
	        <resources>
	            <resource>
	                <directory>src/main/resources.${deploy.type}}</directory>
	                <excludes>
	                    <exclude>*.jsp</exclude>
	                </excludes>
	            </resource>
	            
	            <resource>
	                <directory>src/main/resources</directory>
	            </resource>
	        </resources>
	B、在</build>节点同级添加
	<profiles>
	        <profile>
	            <id>dev
	            <activation>
	                <activeByDefault>true</activeByDefault>
	            </activation>
	            <properties>
	                <deploy.type>dev</deploy.type>
	            </properties>
	        </profile>
	        <profile>
	            <id>beta
	            <properties>
	                <deploy.type>beta</deploy.type>
	            </properties>
	        </profile>
	        <profile>
	            <id>prod
	            <properties>
	                <deploy.type>prod</deploy.type>
	            </properties>
	        </profile>
	    </profiles>
	打开Idea工具的“Maven Projects”面板,即可看到Profiles.列表
	继续在resources文件夹同级别创建resources.dev,resources.beta,resources.prod文件夹
	并复制datasource.properties、logback.xml、mmall.properties、zfbinfo.properties文件到上面三个文件夹中,同时加上不同环境的注释标记,并将resources文件夹下的这4个文件删除。
	打包测试:
	先关闭tomcat服务器
	mvn clean package -Dmaven.test.skip=true -Pdev   (不加-P默认dev)
	检查target/calsses/文件夹下文件的生成状态。
	(也可以点击”Maven Projects”面板的Profiles列表切换)

四、Tomcat集群
	提高服务的性能,并发能力,以及高可用性
	提供项目架构的横向扩展能力
	通过Nginx负载均衡进行请求转发(load balance)
	A、Mac/Linux环境下Tomcat单机部署多应用
1、修改/etc/profile 增加tomcat环境变量
	export CATALINA_BASE=/Users/tomcat1
	export CATALINA_HOME=/Users/tomcat1
	export TOMCAT_HOME=/Users/tomcat1
	
	export CATALINA_2_BASE=/Users/tomcat2
	export CATALINA_2_HOME=/Users/tomcat2
	export TOMCAT_2_HOME=/Users/tomcat2
	执行 source /etc/profile 是配置文件生效
2、第一个tomcat不变
	打开第二个tomcat目录下catalina.sh
	即${tomcat}/bin/catalina.sh
	找到 # OS specific support.$var _must_be set to either true or false.
	在这行下面编辑,新增配置,保存退出
	export CATALINA_BASE=$CATALINA_2_BASE
	export CATALINA_HOMEE=$CATALINA_2_HOME
3、打开第二个tomcat的conf目录server.xml
	即:${tomcat}/conf/server.xml
	注意:3个端口,3个端口,3个端口都要改
	第一个:Server port节点端口号修改(8005是默认的第一个tomcat的8005,修改成9005,
	不叫9005也可以,但是在多个tomcat之间一定不能重复)
	第二个:Connector port=“8080”节点端口号修改 成9080,其他的不要改
	第三个:Connector port=“8009”改成9009
	注意不是紧接着第二个端口后面的
	<!-- Define an AJP 1.3 Connector on port 8009 --> 
 	<Connector port="9009" protocol="AJP/1.3" redirectPort="8443" />

4、分别进入两个tomcat的bin目录,启动tomcat,即进入${tomcat}/bin/ 执行startup.sh
5、检查两个tomcat的启动日志(终端日志) Using CATALINA_BASE:....
6、访问localhost:8080 localhost:9080 测试
B、Windows环境下Tomcat单机部署多应用
1、添加新增的Tomcat相关环境变量
	CATALINA_BASE=C:\tomcat1
	CATALINA_HOME=C:\tomcat1
	TOMCAT_HOME=C:\tomcat1
	
	CATALINA_2_BASE=C:\tomcat2
	CATALINA_2_HOME=C:\tomcat2
	TOMCAT_2_HOME=C:\tomcat2
 2、第一个tomcat不变
	打开第二个tomcat目录bin下catalina.bat
	即${tomcat}/bin/catalina.bat
	打开第二个tomcat目录bin下startup.bat
	即${tomcat}/bin/startup.bat
	替换这两个文件中的
	CATALINA_BASE->CATALNA_2_BASE
	CATALINA_HOME->CATALINA_2_HOME
3、打开第二个tomcat的conf目录下server.xml
	即:${tomcat}/conf/server.xml
	同Linux环境,修改3个端口
4、分别进入两个tomcat的bin目录下,启动tomcat,
	${tomcat}/bin/ 执行startup.bat
	并检查启动日志,访问不同端口进行测试
C、Nginx负载均衡配置,常用策略,场景及特点
	轮询(默认):
	upstream mm.jiangjiesheng.cn{
		server  mm.jiangjiesheng.cn:8080;
		server  mm.jiangjiesheng.cn:9080;
	}
	权重:
	upstream mm.jiangjiesheng.cn{
		server  mm.jiangjiesheng.cn:8080; weight=15;
		server  mm.jiangjiesheng.cn:9080; weight=10;
	}
	ip hash:
	优点:能实现同一个用户访问同一个服务器
	缺点:根据ip hash不一定平均
	upstream mm.jiangjiesheng.cn{
		ip_hash;
		server  mm.jiangjiesheng.cn:8080;  
		server  mm.jiangjiesheng.cn:9080; 
	}
	url hash (第三方插件):
	优点:能实现同一个用户访问同一个服务器
	缺点:根据url hash分配请求会不平均,请求频繁的url会请求到同一个服务器上
		upstream mm.jiangjiesheng.cn{
		server  mm.jiangjiesheng.cn:8080;  
		server  mm.jiangjiesheng.cn:9080; 
		hash $request_uri;
	}
	fair (第三方插件):
	特点:按后端服务器的响应时间来分配请求,响应时间短的优先分配
	upstream mm.jiangjiesheng.cn{
		server  mm.jiangjiesheng.cn:8080;  
		server  mm.jiangjiesheng.cn:9080; 
		fair;
	}
负载均衡参数讲解扩展知识点
	upstream mm.jiangjiesheng.cn{
		ip_hash;
		server  127.0.0.1:8080 down; (down表示当前的server暂时不参与负债)  
		server 127.0.0.1:9080 weight=2; (weight默认为1,weight越大,负载的权重越大)
		server 127.0.0.1:6070;
		server 127.0.0.1:7070 backup; (其他所有的非backup机器down或者忙的时候,
		       		请求backup机器)
	}
修改host:
	Linux环境下:sudo vim /etc/hosts
	WIndows环境下:C:\Windows\System32\drivers\etc\hosts
启动Nginx:
	Linux:/usr/local/nginx/sbin/nginx.sh
	Windows:${nginx}/nginx.exe
配置Nginx
根据 include vhost/*.conf; 来增加负载均衡配置
upstream mm.jiangjiesheng.cn{
	server mm.jiangjiesheng.cn:8080 weight =1;
	server mm.jiangjiesheng.cn:9080 weight =1;
	#server 192.168.1.1:8080;
	#server 192.168.1.2:9080;
}
server {
    default_type 'text/html';
    charset utf-8;
    listen 80;
    autoindex on;
    server_name mm.jiangjiesheng.cn www.mm.jiangjiesheng.cn ;
    access_log /usr/local/nginx/logs/access.log combined;
#如果是Windows环境,也要改路径
    index index.html index.htm index.jsp index.php;
    #error_page 404 /404.html;
    if ( $query_string ~* ".*[\;'\<\>].*" ){
        return 404;
    }

    location ~ /(mmall_fe|mmall_admin_fe)/dist/view/* {
        deny all;
    }
    location / {
        proxy_pass http://mm.jiangjiesheng.cn;      #这里很关键
        add_header Access-Control-Allow-Origin *;
    }
}
重新加载Nginx配置操作
	${nginx}/sbin 执行 sudo ./nginx -s reload
	(Windows中需要重新打开一个创建reload配置)
	测试不同服务器下配置的不同图片来进行测试负载均衡效果

实际操作:
	cd /usr/mylibs/tomcat7
	cp apache-tomcat-7.0.73 -r apache-tomcat-7.0.73-2 (加上参数-r)
	启动apache-tomcat-7.0.73 
	(注意关闭防火墙测试访问 systemctl stop firewalld.service)
	在apache-tomcat-7.0.73-2 的webapps中的ROOT中index.jsp文件
	添加“这是tomcat2启动的”标识
	配置/etc/profile
	CATALINA_BASE=/usr/mylibs/tomcat7/apache-tomcat-7.0.73
	CATALINA_HOME=/usr/mylibs/tomcat7/apache-tomcat-7.0.73
	TOMCAT_HOME=/usr/mylibs/tomcat7/apache-tomcat-7.0.73
	
	CATALINA_2_BASE=/usr/mylibs/tomcat7/apache-tomcat-7.0.73-2
	CATALINA_2_HOME=/usr/mylibs/tomcat7/apache-tomcat-7.0.73-2
	TOMCAT_2_HOME=/usr/mylibs/tomcat7/apache-tomcat-7.0.73-2
	source /etc/profile 
	打印环境变量
	echo $CATALINA_2_BASE
	/usr/mylibs/tomcat7/apache-tomcat-7.0.73-2
	
	cd /apache-tomcat-7.0.73-2/bin
	如果是Windows环境,则编辑catalina.bat
	如果是Linux环境,则编辑catalina.sh
	编辑catalina.sh
	搜索 /OS s
	添加 export CATALINA_BASE=$CATALINA_2_BASE
	export CATALINA_HOME=$CATALINA_2_HOME
	
	cd /apache-tomcat-7.0.73-2/conf
	修改server.xml配置3个端口
	
	分别启动tomcat1和tomcat2
	并分别访问两个端口
	http://192.168.43.218:8080/
	http://192.168.43.218:9080/
	
	进入nginx conf/vhost
	cp mm.gestruectrl.com.conf 192.168.43.218.conf
	vi  192.168.43.218.conf
	编辑监听的地址为 192.168.43.218 ,location打到http://127.0.0.1:8080
	./sbin/nginx -s reload
	访问192.168.43.218进行测试
	
继续配置 192.168.43.218.conf

 upstream 192.168.43.218{
        server 192.168.43.218:8080 weight=1;
        server 192.168.43.218:9080 weight=3;#表示是8080的3倍访问概率
}

server {
    default_type 'text/html';
    charset utf-8;
    listen 80;
    autoindex on;
    server_name 192.168.43.218;
    access_log /usr/local/nginx/logs/access.log combined;
    index index.html index.htm index.jsp index.php;
    #error_page 404 /404.html;
    location / {
        proxy_pass http://192.168.43.218;      #转发到负载均衡
        add_header Access-Control-Allow-Origin *;
    }
}
继续nginx -s reload 后进行负载均衡测试 192.168.43.218 
(看看能否进入不同的首页)

五、Redis
	Redis-REmote DIctionary Server 
	(使用ANSI C语言编写的开源数据库)
	高性能的key-value数据库
	内存数据库,支持数据持久化
	官网 redis.io 中文redis.cn
A、Redis常用数据类型
	string、hash、list(链表)、set、sorted set(有序集合)
	B、编码方式
	raw int ht zipmat linkedlist ziplist intset
C、安装redis
	http://download.redis.io/releases/
	下载2.8.0 http://download.redis.io/releases/redis-2.8.0.tar.gz
	 windows版本https://github.com/MicrosoftArchive/redis/releases
	windows版本下载链接
				https://github.com/MicrosoftArchive/redis/releases/
	download/win-2.8.2402/Redis-x64-2.8.2402.zip
	cd /usr/mylibs/
	mkdir redis
	wget http://download.redis.io/releases/redis-2.8.0.tar.gz
	tar -xvf redis-2.8.0.tar.gz
	cd redis-2.8.0 
	make
	make test (测试)
	You need tcl 8.5 or newer in order to run the Redis test
	解决http://blog.csdn.net/luyee2010/article/details/18766911
	wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz  
	sudo tar xzvf tcl8.6.1-src.tar.gz  -C /usr/local/  
	cd  /usr/local/tcl8.6.1/unix/  
	sudo ./configure  
	sudo make  
	sudo make install

D、服务端启动
	redis-server
	redis-server ${redis.conf}
	redis-server --port ${port}
E、客户端启动与关闭
	redis-cli
	redis-cli -p ${port}
	redis-cli -a ${password}
	redis-cli -p ${port} -h ${ip} -a ${password}
	关闭 
	redis-cli shutdown
	redis-cli -p ${port} shutdown
	redis-cli -h ${ip} shutdown
	redis-cli -p ${port} -h ${ip} shutdown
F、Redis基础命令
	info ping quit save dbsize select flushdb flushall
G、Redis键命令
	set del exists expire ttl type randomkey
H、服务启动与连接测试
	redis-cli 和redis-server执行文件位置: /usr/mylibs/redis/redis-2.8.0/src
	cd src > ./redis-server & 回车 (后台启动) 
	关闭 kill -9 PID号
	(查进程  ps -ef | grep redis)
	(或者 ps aux )
	重新开启一个窗口 
	./redis-cli 
	keys *

更多总结:
	save 人为触发保存
	直接终止进程 kill -9 PID (可能导致数据丢失,不推荐)
	redis服务安全退出 ./redis-cli [-p 6379]shutdown

修改默认启动端口
	 6379 端口在redis.conf文件中,修改完成后启动时需要指定配置文件
	(另:搜索文件位置 tree | grep conf)
	启动./redis-server ../redis.conf

启用访问密码
	同样在redis.conf,搜索 /requirepass ,(N 下一个 ?从下往上 / 从上往下)
	启动./redis-server ../redis.conf
	 ./redis-cli [-p 6379] -a root 
注:设计到主从时,也需要配置密码 搜索 masterauth <master-password>

redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out 使用两个窗口打开redis服务器就能连接成功
相关问题进供备用参考
https://www.cnblogs.com/bestmystery/p/6371229.html 

I、
	基础命令
	info 查看状态
	select 1-15 选择数据库 (连接提示符上有数据库显示127.0.0.1:6379[10])
	flushdb 清除当前database的数据
	flushall 清除所有database的数据
	dbsize 查看当前数据库的数据条数
	monitor 监控

键命令实战
	set test test
	keys * (查看当前选择的数据库的所有的key)
	del test
	exists test
	expire a 60 (设置a值有效时间为60秒,注意这里不是值,这里设置已存在key)
	setex a 60 aa (这里是一步设置k-v和过期时间)
	psetex a 10000 aa (这里的时间为毫秒,10000毫秒表示10s)
	ttl test (查看当前key的剩余时间 -1表示没有过期时间 -2表示已过期,且值变成nil)
	type a (返回当前值的类型)
	hset hashkey key value
	randomkey 随机返回当前数据库存在的key
	rename a d (把key a重命名成key b,如果重命名成已经存在的key c,
	那么c会被替换)
	renamenx (nx结尾的命令会先判断,若已存在b,则重命名失败)
	getrange a 0 2 (获取key a 的value的第0到第2位,前后都是闭区间)

J、数据结构-string
	getset a aaa (先返回原来key的value,然后设置新的value)
	mset a1 a1 b1 b1 c1 c1 (同时设置3组k-v)
	mget a1 b1 c1 (同时获取多个key的value)
	setnx a aa (先判断是否已存在key a,nx结尾都是先判断)
	msetnx a aa b bb (具有事务原子性,要么全成功,要么全失败)
	strlen a (获取key a的value的长度)
	set e 1  //incr e (设置int ,incr 表示++1),incrby e 100 (每次增量为100)
	//decr 和 decrby同上,另外使用 ±数量 来实现递增或递减
	append a xxx (在key a 的value上追加)
	(更多见“键命令实战”)
K、数据结构-hash
	hset map name jiang
	hexists map name (判断map中是否有name的key)
	hget map name
	hset map age 28
	hgetall map (获取所有的key 、value (非键值对形式))
	hkeys map (获取所有key)
	hvals map(获取所有的value)
	hlen map (获取k-v个数)
	hmget map name age (同时获取多个指定key的value)
	hmset map newname newnamevalue newage newagevalue(同时设置多个k-v)
	hdel map key1 key2 (同时删除多key)
	hsetnx map name newjiang (set前先判断)
L、数据结构-list
	lpush listobj 1 2 3 4 5 6
	llen listobj (获取个数)
	lrange listobj 0 20 (最新的数据在最上)
	lset listobj 0 100 (设置索引0的元素的值为100)
	lindex listobj  0 (根据索引取值)
	lpop listobj (移除第一个)
	rpop listobj(移除最后一个 )
M、数据结构-set(无序、可排重、时间复杂度O(1) )
	//时间复杂度https://zhidao.baidu.com/question/545786195.html
	sadd set a b c d
	scard set (获取set集合元素的数量)
	rename set set1
	sadd set2 c d e f
	smembers set1 (查看集合的成员)
	sdiff set1 set2 (差集 set1减去set2,abcd - cdef = ab (保留前者独立存在的))
	sdiff set2 set1(cdef-abcd=fe (保留前者独立存在的))
	sinter set1 set2 (交集 =cd)	
	sunion set1 set2 (并集 = 所有值,但无重复)
	srandmember set1 2(随机获取集合中的2个元素)
	sismember set1 a (判断是不是集合中的元素)
	srem set1 a b(移除集合中的a b两个元素)
	spop set2 (随机移除一个元素并返回该元素(这个可用用于订单号获取))
N、数据结构-sortedset(有序集合,复杂度O(1))
	zadd sortedset1 100 a 200 b 300 c (成员不可重复,但分数可重复)
	type sortedset1 (=zset)
	rename sortedset1 sortedset
	zcard sortedset (查看集合中个数)
	zscore sortedset a (查看a的分数 = 100)
	zcount sortedset 0 201 (查看符合0到201前后闭区间的个数=2)
	zrank sortedset a (获取索引(会先按值排序) =0)
	zincrby sortedset 1000 a (给a添加1000)
	zrank sortedset a (在上一步操作之后(会先按值排序) =3)
	zrange sortedset 0 100 (= “b”,"c",“a”, 为什么有a?)
	zrange sortedset 0 100 withscores(会分别携带相应的分数)

六、Redis+Cookie+Jackson+Filter实现单点登录(SSO-Single Sign On)-集群调试(多进程debug)
	相关技术点:项目集成Redis客户端Jedis | Redis连接池构建及调试 | Jedis API封装及调试 | Jackson封装JsonUtil及调试 | Jackon ObjectMapper源码解析 | Cookie封装及使用 | SessionExpireFilter重置session有效期 | 用户session相关模块重构 | Guava cache迁移Redis缓存 | Multi-Process Debug | JedisPoolConfig源码解析 | JedisPool源码解析 | JedisPool回收资源 | 封装RedisPool | 封装RedisPoolUtil | Jsckson封装JsonUtil及封装 | 多泛型序列化和反序列化 | 
	redis 搜索 (降低版本至2.6.0)
	http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22redis.clients%22%20AND%20a%3A%22jedis%22
	http://search.maven.org/#artifactdetails%7Credis.clients%7Cjedis%7C2.6.0%7Cjar
	<dependency>
	    <groupId>redis.clients</groupId>
	    <artifactId>jedis</artifactId>
	    <version>2.6.0
	</dependency>
	 //Ctrl Alt T 快速弹出代码
	java序列化/反序列化之xstream、protobuf、protostuff 的比较与使用例子
	https://www.cnblogs.com/xiaoMzjm/p/4555209.html (protostuff 最优)
	单步调试技巧: 光标移动到要调试的对象上,Ctrl + U ,在弹出的面板中可以输入表达式,例如.size() ,.get(0);
	tomcat 运行war exploded 与war(war 会打包到tomcat/webapps/ROOT)。
	com.mmall.common.RedisPool.java
	com.mmall.util.RedisPoolUtil.java
	com.mmall.util.JsonUtil.java
	
	集群调试(多进程debug)
	复制整个项目并重命名后打开
	在第二个项目中新建tomcat2(tomcat也需要复制一个),打包选择war。各个端口都增加1000。启动tomcat2后tomcat1需要重启(两个tomcat也不要同时启动)
	
	继续部署windows环境下的nginx集群(负载均衡、反向代理)跳过host配置的虚拟域名,直接使用ip。
	
	两个项目代码同步技巧:
	第一个项目代码修改push后,第二个项目先执行git checkout .  ,然后执行git pull
	git checkout . #本地所有修改的。没有的提交的,都返回到原来的状态
	另:
	git stash #把所有没有提交的修改暂存到stash里面。
	(含有pop的用法见http://blog.csdn.net/wh_19910525/article/details/7784901)
	git reset --hard HASH #返回到某个节点,不保留修改。
	git reset --soft HASH #返回到某个节点。保留修改
	
	在线接口调试使用QQ浏览器中Restlet Client 插件,账号 dev@jiangjiesheng.cn
	
	多个server name 的域名或ip 之间使用空格连接
	 192.168.43.218.80-linux.conf
	
	192.168.43.248.80-windows.conf
	
	
	./nginx -s reload
	 com.mmall.util.CookieUtil.java > COOKIE_DOMAIN 设置
	 private final static String COOKIE_DOMAIN="192.168.43.248";// .jiangjiesheng.cn
	 Cookie ck = new Cookie(COOKIE_NAME, token);
	 ck.setDomain(COOKIE_DOMAIN);
	 ck.setHttpOnly(true);//屏蔽使用脚本获取cookie信息
	        ck.setPath("/");
	        //如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页有效
	        ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久,0表示删除,单位秒 
	
	Jackson序列化与反序列化
	com.mmall.util.JsonUtil
	public static  T stringToObj(String str, Class clazz)
	public static  T stringToObj(String str, TypeReference typeReference)
	public static  T stringToObj(String str, Class colectionClass, Class... elementClasses)
	
	继续处理忘记密码两个接口的签名验证(token)的缓存问题,Guava cache 转移到Redis服务器。

七、Redis分布式
	Redis分布式算法原理
	ShardedJedis源码解析(分片)
	分布式Shard Redis API
	Redis分布式环境验证 
	集群和分布式概念
1、Redis分布式算法原理
	传统分布式算法
	Consistent hashing一致性算法原理
	Hash倾斜性(分布不够均匀)
	虚拟节点 (改善命中率低的问题)
	Consistent hashing命中率 (1-n/(n+m) * 100%)  服务器台数n,新增节点m(随着分布式集群越来越大,命中率越来越大,也能减少hash倾斜性)
	
	
	test.jpg > hash(test.jgg)%3 (调整节点后 命中率会变低)
	Consistent hashing一致性算法原理最早在1997年的论文中提出
	
	环形hash空间 > 把4个对象映射到hash空间上 > 考虑4个对象object1 ~ object4 > 通过hash函数计算出hash值key > 把cache映射到hash空间 > 基本思想就是把对象和cache都映射到同一个hash数值空间中,并且使用相同的hash算法 > hash(cache A) = key A ...hash(cache C) = key C 

2、Redis分布式服务端及客户端启动
	/usr/mylibs/redis/redis-2.8.0/
	修改两个Redis的配置文件redis.conf > 端口分别修改为6379 6380 > 通过使用配置文件
	路径作为参数启动redis-server > ./redis-server ${redis[0-1]}的${redis.conf}

3、 cp -rf redis-2.8.0 redis-2.8.0-2 复制,开启两个putty窗口
	启动redis 1 : ./src/redis-server & 回车 
	修改第二个redis端口并启动 
	vim redis.conf 
	/6379 搜索
	N 下一个
	改成6380
	 ./src/redis-server redis.conf &  回车 
	再开两个putty窗口,运行redis-cli
	/usr/mylibs/redis/redis-2.8.0
	./src/redis-cli 
	/usr/mylibs/redis/redis-2.8.0-2
	./src/redis-cli -p 6380 (p小写)

4、 封装RedisShardedPool
	查看ShardedJedis类的继承关系 > 光标放在ShardedJedis上,
	右击Diagrams-->Show Diagram (虚线表示实现接口,实线表示继承)
	com.mmall.common.RedisShardedPool
	并在main方法中测试
	public static void main(String[] args) {
	ShardedJedis jedis = pool.getResource();
	for (int i = 0; i < 10; i++) {
	jedis.set("key" + i, "value" + i);
	 }
	returnResource(jedis);
	// pool.destroy();//临时调用,销毁连接池中的所有连接
	System.out.println("program is end");
	}
	
	分别在两个redis-cli 中的查看 keys * 
	
	com.mmall.util.RedisShardedPoolUtil.java
	
5、集群和分布式概念
	集群是个物理形态 10个服务器 处理10个任务,每个任务需要一个小时
	分布式是工作方式 (似乎从软件层面出发)
	
	tomcat集群 
	redis分布式
	
八、Spring Session实现单点登录(对业务没有入侵)
	Spring Session简介 官方地址 文档 源码 
	Redis Desktop Manager介绍及使用
	Spring Session提供了一套创建和管理Servlet HttpSession的方案,并提供了集群Session(Clustered Session)功能,默认采用外置的Redis来存储Session数据,以此解决Session共享的问题。
	https://projects.spring.io/spring-session/
	https://docs.spring.io/spring-session/docs/current/reference/html5/
	https://github.com/spring-projects/spring-session
	https://github.com/spring-projects/spring-session/tree/1.2.x/samples
	
	项目集成:引入Spring Session pom > 配置JedisConnectionFactory > 配置DelegatingFilterProxy (委托) > 配置RedisHttpSessionConfiguration > 配置DefaultCookieSerializer > 配置JedisPoolConfig
	
	Redis Desktop Manager工具下载
	https://redisdesktop.com/download
	可以使用类似命名空间的方式优化redis的key 例如 set mmall:user:username jiang
	这时在Redis Desktop Manager中查看时会有文件夹的形式来展示,
	方便区分不同业务环境的数据
	
	添加pom.xml > 
	搜索 http://search.maven.org/#search%7Cga%7C1%7Cspring-session-data-redis
	选择 1.2.0 Release版本 http://search.maven.org/#artifactdetails%7Corg.springframework.session%7Cspring-session-data-redis%7C1.2.0.RELEASE%7Cjar
	
	<dependency>
		<groupId>org.springframework.session</groupId>
		<artifactId>spring-session-data-redis</artifactId>
		<version>1.2.0.RELEASE</version>
	</dependency>
	
	右击pom.xml > maven > reimport
	idea 中连续输入两个Shift,可以search everywhere
	修改web.xml 新增applicationContext-spring-session.xml 并在applicationContext.xml 中import进来。
	错误:
	org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘springSessionRepositoryFilter’ defined in class path resource[org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: 
	同时修改pom.xml 
	<org.springframework.version>4.0.0.RELEASE</org.springframework.version>
	改成
	<org.springframework.version>4.0.3.RELEASE</org.springframework.version>
	
	然后reimport pom.xml 
	再使用maven clean一下项目
	以上两步都是必须的

Spring resource中的properties读取另一个文件中的properties
方法1:
	<context:property-placeholder location="classpath:conn.properties"/><!-- 加载配置文件 -->  
	特别注意
	不同的配置文件中好像一共只能加载一个 context:property-placeholder location 配置
	多个配置应该可以使用通配符配置 
	<context:property-placeholder location="classpath*:conf/conf*.properties"/>  
	http://blog.csdn.net/white__cat/article/details/56485392

方法2:
	<bean id="propertyConfigurer"	class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
			<value>classpath:datasource.properties</value>
			</list>
			<!--或者 可加载多个资源<array>
			<value>classpath:datasource.properties</value>
			</array>-->
		</property>
	</bean>

断点调试时,单步到某一对象上 Ctrl T 也可以查看具体有哪些实现类。

九、Spring MVC全局异常
修改applicationContext.xml、dispatcher-servlet.xml
//测试全局异常
        int i = 0;
        int j = 666 / i;
1、新建com.mmall.common.ExceptionResolver.java
	并使用注解注入到Spring容器中
	@Repository 习惯用在DAO层上
	@Service 习惯用在Service层上
	@Component 除以上的其他情况下
	@Slf4j
	//@Repository //习惯用在DAO层上
	//@Service //习惯用在Service层上
	@Component //除以上的其他情况下
	public class ExceptionResolver implements HandlerExceptionResolver {
	
	    @Override
	public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
	log.error("{} Exception ", httpServletRequest.getRequestURI(), e);
			ModelAndView modelAndView = new ModelAndView(new 
			MappingJacksonJsonView());
			//pom.xml中的jackson-mapper-asl
			//当使用时Jackson2.x的时候使用MappingJackson2JsonView,这里使用1.9.x
			modelAndView.addObject("status", ResponseCode.ERROR.getCode());
			modelAndView.addObject("msg", "接口异常,详情请查看服务器端日志");
			modelAndView.addObject("data",e.toString());
			return modelAndView;
		}
	}

十、Spring MVC拦截器
	Spring MVC拦截器流程图
	
	  -->      Http请求      -->      DispatcherServlet
	 	    ↓
	客户端	<--拦截器相应信息<--	HandlerIntercepter       -->验证通过--> x.do
	       (拦截器)						↓
	 								↓
	 <-----------------------------------------------------------------------------------
	       (Controller相应信息)

新建拦截器
	com.mmall.controller.common.interceptor.AuthorityIntercepter
	特别注意
	 @Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		log.info("preHandle ");
		String methodName = handlerMethod.getMethod().getName();
		String calssName = handlerMethod.getBean().getClass().getSimpleName();
		if(xxxx){
			//这里需要对login 和 富文本上传的接口做特殊处理
			response.reset();//这里添加reset(),否则报异常
			       response.setCharacterEncoding("UTF-8");//必须
			       response.setContentType("application/json;charset=UTF-8");
			PrintWriter out = response.getWriter();
			out.print(JsonUtil.obj2String(resultMap));
			out.flush();
			out.close();
			return false;//返回false 则不会调用controller中方法
		}else{
		    return true;//true 传递请求 ,false 不传递,返回false 则不会调用controller中方法,和安卓中的事件冒泡 正好相反
		}
	 }

并在dispatcher-servlet.xml中配置
<mvc:interceptors>
        <!-- 定义在这里的,所有的都会拦截-->
        <!--manage/a.do /manage/*-->
        <!--manage/b.do /manage/*-->
        <!--manage/product/save.do /manage/**-->
        <!--manage/order/detail.do /manage/** -->
       		 <mvc:interceptor>
           		 <mvc:mapping path="/manage/**"/>
    <!--也可以通过代码实现跳过拦截-推荐-->
            		 <mvc:exclude-mapping path="/manage/user/login.do"/>
           		 <bean class="com.mmall.controller.common.interceptor.AuthorityIntercepter"/>
       	 	</mvc:interceptor>
  	  </mvc:interceptors>

最后改造com/mmall/controller/backend下的几个Controller

十一、Spring MVC RESTful原理及改造
配置Spring MVC RESTful:
进入web.xml > 修改 > 
	<!--<servlet-mapping>-->
	<!--<servlet-name>dispatcher</servlet-name>-->
	<!--<url-pattern>*.do</url-pattern>-->
	<!--</servlet-mapping>-->
	    
	<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern>
	</servlet-mapping>

改造	com.mmall.controller.portal.ProductController下的两个接口
(不是所有接口都适合RESTful 参数特别多的时候就不好)
1、
	@RequestMapping(value = "/{productId}",method = RequestMethod.GET)
	@ResponseBody
	public ServerResponse detailRESTful(@PathVariable Integer productId) {
	 return iProductService.getProductDetail(productId);
	}
	http://localhost:8080/product/28
	http://localhost:8080/product/detail.do?productId=28  (原来)
2、
	对于参数注解也要换一下@RequestParam 换成@PathVariable
	(//普通请求的参数参数如果是json,可以使用@RequestBody来接收)
	还有默认是@PathVariable的默认值设置
	两个核心参数的冲突(keyword,productId)
	http://localhost:8080/product/手机/100012/1/10/price_asc
	http://localhost:8080/product/product/100012/1/10/price_asc
	http://localhost:8080/product/keyword/100012/1/10/price_asc
	//实测不能像ASP.NET MVC中直接 "/{productId}/{orderBy=price_asc}"来设置默认值

十二、Spring Schedule定时关单
1、Spring Schedule Cron表达式
	Cron表达式的格式:秒分时日月周年(可选)
	字段名					允许的值				允许的特殊字符
	1> Seconds    				0-59						, - * /
	2> Minutes				0-59						, - * /
	3> Hours					0-23						, - * /
	4> Day-of-Month 			1-31						, - * / L W C
	5> Month 				1-12 或 JAN-DEC			, - * /
	6> Day-of-Week 			1-7 或 SUN-SAT			, - * / ? L C #
	7> Year (可选字段)			留空,1970-2099 			, - * /
	
	特殊字符
	特殊字符 意义
	* 匹配所有的值,如:*在分钟的字段城里表示每分钟
	? 只在日期城和星期城中使用,它被用来指定“非明确的值”
	- 指定一个范围,如:“10-12”在小时城意味着“10点、11点、12点”
	, 指定几个可选值,如:“MON,WED,FRI" 表示“星期一、星期三、星期五”
	/ 指定增量,如:“0/15”在秒城意思是每分钟的0,15,30和45秒,“5/15”在分钟城表示每小时的5,20,35和50,符号“*”在“/”前面(如:*/10) 等价于0在“/”前面(如:0/10)
	L 表示day-of-month和day-of-week城,但在两个字段中的意思不同,例如dayof-month城中表示一个月的最后一天。如果在day-of-week城表示‘7’或者'SAT',如果在day-of-week城中前面加上数字,它表示一个月的最后几天例如‘6L’就表示一个月的最后一个星期
	W 只允许日期城出现,这个字符用于指定日期的最近工作日。例如:如果你在日期城中写“15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发,如果15好是周日,则任务会在周一也就是16号触发如果是在日期城填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的,字符“留”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的
	LW L和W可以在日期域中联合使用,LW表示这个月最后一周的工作日
	# 只允许在星期城中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周),“2#1”表示本月第周的星期一,“4#5”表示第五周的星期三
	C 允许在日期城和星期城出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联则等价于所有包含的“ 日历”, 如:日期域是“5C”表示关联“ 日历”中第一天,或者这个月开始的第一天的后5天,星期城是“1C" 表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)
2、常用cron表达式介绍
	秒分时日月周年(可选)
	0 0 0 * * ?	 	每天0点一次
	0 0 23 * * ?	每天23点一次
	0 * /1 * * * ?	每1分钟(每个1分钟的整数倍)
	0 0 */6 * * ?	每6小时(每个6小时的整数倍)
	0 0 */1 * * ?	每1小时(每个1小时的整数倍)
3、Spring Schedule Cron 在线生成器
	但是不要使用 http://www.pppet.net/  (有bug)
4、Spring Schedule Cron配置
	 //Code
	
	@Component (加在类上)
	@Scheduled(加在方法上)
	例如 @Scheduled(cron="0 * /1 * * * ?")

5、MySQL行锁、表锁
	SELECT ... FROM UPDATE (悲观锁)
	(乐观锁在表中增加一个字段)
	乐观锁和悲观锁的区别(最全面的分析):
	http://blog.csdn.net/rexct392358928/article/details/52230737
	使用InnorDB引擎
	Row-Level Lock(明确的主题)
	Table-Level Lock(无明确的主题)
	mmall_pruduct表,有id和name,id是主键
	明确指定主键,并且有结果集,Row-Level Lock
	SELECT * FROM mmall_product WHERE id =`66` FOR UPDATE; 
	 > 产生行锁
	
	明确指定主键,并且无结果集,无Lock
	SELECT * FROM mmall_product WHERE id = ' -100' FOR UPDATE ;
	 > 不会上锁
	
	无主键 Table-Level Lock
	SELECT * FROM mmall_product WHERE name = `iphone` FOR UPDATE;
	 > name 不是主键,会产生表锁
	
	主键不明确 Table-Level Lock
	SELECT * FROM mmall_product WHERE id <> `66` FOR UPDATE; (<> 换成like也是)
	 	> 产生表锁
6、配置applicationContext.xml
	新增<task:annotation-driven/>,注意annotation-driven选择 
	xmlns:task="http://www.springframework.org/schema/task"。
	相关的命名空间一定不能错
	
	新建task包
	com.mmall.task.CloseOrderTask
	注意
	1、< 转义
	 <select id="selectOrderStatusByCreateTime" 
	resultMap="BaseResultMap" parameterType="map">
	SELECT
	<include refid="Base_Column_List"/>
	FROM mmall_order
	 WHERE status = #{status}
	<![CDATA[
	and create_time <= #{date}
	 ]]>
	 order by create_time desc
	 </select>
	2、悲观锁 、行锁
	<select id="selectStockByProductId" parameterType="java.lang.Integer" 
	resultType="int">
	SELECT stock
	 FROM mmall_product
	WHERE id = #{id}
	for update
	/* (主键 + for update 实现行锁)
	 这里where一定要是主键,否则变成悲观锁(表锁)*/
	</select>
	3、代码实现关键
	@Slf4j
	@Component
	public class CloseOrderTask {
	@Autowired
	private IOrderService iOrderService;
		@Scheduled(cron = "0 */1 * * * ?")  //每分钟执行一次
			public void closeOrderTaskV1() {
		log.info("------------关闭订单定时任务启动------------");
			int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour", "2"));
			 iOrderService.closeOrder(hour);
			log.info("------------关闭订单定时任务结束------------");
		}
	}

7、分布式任务调度
tomcat集群环境下一个服务执行就行,不需要每台服务器都执行。将采用Redis分布式锁解决同时访问定时关单任务的问题。

十三、Redis分布式锁核心代码
1、v2版本(存在死锁缺陷)

@Scheduled(cron = "0 */1 * * * ?")  //每分钟执行一次
public void closeOrderTaskV2() {
	log.info("------------关闭订单定时任务启动------------");
	Long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout", "5000"));
	/**
	*  核心原理
	*/
	Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, String.valueOf(System.currentTimeMillis() + lockTimeout));
	//只有key值的话,不能set成功,据此判断当前以后有tomcat服务器在执行定时关单任务
	if (setnxResult != null && setnxResult.intValue() == 1) {
		//如果返回值为1,代表设置成功,获取锁(可写)
		closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
	} else {
		log.info("------------没有获得分布式锁:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
	}
		log.info("------------关闭订单定时任务结束------------");
}

private void closeOrder(String lockName) {
	RedisShardedPoolUtil.expire(lockName, 50);//50秒,防止死锁 ,但是这里依然可能会有死锁发生(例如代码没有走到这里时,tomcat服务器关闭了)
	//可使用@PreDestroy 删除锁,(tomcat shutdown之前执行此注解方法 ,但是如果需要删除的锁非常多,则执行很慢,另外如果直接kill tomcat的进程,则该方法不会执行)
	log.info("------------获取{},ThreadName:{}------------", lockName, Thread.currentThread().getName());
	int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour", "2"));
	//iOrderService.closeOrder(hour);
	RedisShardedPoolUtil.del(lockName);
	log.info("------------释放{},ThreadName:{}------------", lockName, Thread.currentThread().getName());
	        log.info("===============================================");
}

@PreDestroy
public void delLock() {
RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
 }

2、v3版本 核心 重要 Redis分布式锁 (多重防死锁)
@Scheduled(cron = "0 */1 * * * ?")  //每分钟执行一次
public void closeOrderTaskV3() {
	log.info("------------关闭订单定时任务启动------------");
	Long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout", "5000"));
	/**
	* 核心原理
	*/
	Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, String.valueOf(System.currentTimeMillis() + lockTimeout));
	//只有key值的话,不能set成功,据此判断当前以后有tomcat服务器在执行定时关单任务
	if (setnxResult != null && setnxResult.intValue() == 1) {
		 //如果返回值为1,代表设置成功,获取锁(可写)
		 closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
	} else {
		   //log.info("------------没有获得分布式锁:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
		   //核心:未获取到锁,继续判断,判断时间戳,看是否可以重置并获取到锁
		   String lockValueStr = RedisShardedPoolUtil.get(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
		if (lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)) {
			//这时候锁可以失效了。
			String getSetResult = RedisShardedPoolUtil.getSet(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, String.valueOf(System.currentTimeMillis() + lockTimeout));
			// lockValueStr 与 getSetResult可能是不相等的,redis分布式环境下
			// getSetResult 是获取的最新值
			// 这里set了一个新的值,并获取了一个旧的值
			if (getSetResult == null || (getSetResult != null && StringUtils.equals(lockValueStr, getSetResult))) {
				//正在获取到锁
				closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
			} else {
				log.info("------------没有获得分布式锁:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
			}
		}else{
			log.info("------------没有获得分布式锁:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
		}
	}
	        log.info("------------关闭订单定时任务结束------------");
}


十四、Redisson框架项目集成
1、Redisson介绍
	Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid )
	Redisson在基于NIO的Netty框架上。充分的利用了Redis键值救据库提供的一系列优势
	使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统
	的能力,大大降低了设计和研发大规模分布式系统的难度
	同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作
2、Redisson官网
	https://redisson.org/
	Git https://github.com/redisson/
	Wiki https://github.com/redisson/redisson/wiki
	https://github.com/redisson/redisson/wiki/目录
	3、Redission框架集成
	https://github.com/redisson/redisson#quick-start
	<dependency>
	   <groupId>org.redisson</groupId>
	   <artifactId>redisson</artifactId>
	   <version>2.9.0</version>
	</dependency> 

	
	<dependency>
	    <groupId>com.fasterxml.jackson.dataformat</groupId>
	    <artifactId>jackson-dataformat-avro</artifactId>
	    <version>2.9.0</version>
	</dependency>

CountDownLatch -> 一个任务多了线程处理,并等待,最后异步变成同步

十五、Spring Schedule+Redisson构建分布式任务调度
	1、Redisson初始化
	2、Redisson分布式锁实战
	3、Redisson分布式锁实战解决wait_time之坑
	4、Redis主从配置
	5、Redisson分布式锁实战之Debug调试及锁介绍
	代码
	com.mmall.common.RedissonManager
	注意添加注解,是否无法AutoWried
@Slf4j
@Component 

//static {  //TODO }
//保持了方法内操作的唯一性。 适合用一些加载jni操作。 保证只操作一次,类似Application.
//static{} 内的操作是走在所以当前class 内方法的最前端
@PostConstruct
private void init() {//Redisson初始化调用,也可使用static{}
	try {//Ctrl Alt T 快速调出
		config.useSingleServer().setAddress(new StringBuilder().append(redis1Ip).append(":").append(redis1Port).toString());
		redisson = (Redisson) Redisson.create(config);
		log.info("Redisson初始化结束");
	} catch (Exception e) {
		log.error("Redisson init error",e);
		e.printStackTrace();
	}
}

//定时关单第四个版本优化 使用Redisson框架,竞争分布式锁
/**
* 使用Redisson处理
 */
 @Scheduled(cron = "0 */1 * * * ?") //每分钟执行一次
public void closeOrderTaskV4() {
	//RLock 分布式锁
	RLock lock = redissonManager.getRedisson().getLock(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
	Boolean getLock = false;
		try {
			//long waitTime, long leaseTime, TimeUnit unit
			//waitTime 设置>0的值时还是有可能分布式Redis都获取到锁。所有使用0
			//waitTime 在这里可以解释为关单任务执行的总时间 
		if (getLock = lock.tryLock(0, 50, TimeUnit.SECONDS)) {
			 log.info("------------Redisson获取搭到分布式锁:{},ThreadName:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, Thread.currentThread().getName());
			int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour", "2"));
			//   iOrderService.closeOrder(hour);//直接关单
		 } else {
			log.info("------------Redisson没有获取到分布式锁:{},ThreadName:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, Thread.currentThread().getName());
		}
	} catch (InterruptedException e) {
		log.error("Redisson分布式锁获取异常", e);
		e.printStackTrace();
	} finally {
		if (!getLock) {//等待下一次任务调度
		return;
	}
		lock.unlock();//已经获取到锁时要释放
		log.error("Redisson分布式锁释放锁");    
	}
}

生产环境送closeOrderTaskV3()

	Redis主从同步配置:(redis-2.8.0 6379 主 --> 负责写, redis-2.8.0-2 6380 从 --> 只读)
	打开redis-2.8.0-2 的配置文件 > cd /usr/mylibs/redis/redis-2.8.0-2 > vi redis.conf > 搜索 
	/slaveof >  插入 slaveof 127.0.0.1 6379 (注意:这里的ip不是给外部调用,6379 为主,
	6380为从,slaveof 可解读为6379的从服务器) >  保存wq。
	
	启动顺序:先启动Redis主服务器,./src/redis-server & ,回车 (默认配置即可) ,然后启动
	Redis从服务器redis-2.8.0-2,./src/redis-server ./redis.conf & 回车,
	从服务器提示 MASTER <-> SLAVE sync started ......
		
	验证:分别连接主从服务 (从加 -p 6380 参数) >  分别执行flushdb清空数据库0 > 在主客
	户端执行 set a a > keys * >在从客户端执行 keys * > 发现也有了a的key > 继续在从客户端执行
	set b b > 报错: (error) READONLY You can't write against a read only slave > 从服务只读 > 
	说明配置成功
	
	说明:由于从服务只读不可写,所以会和RedisShardedPool.java(Redis分片读写冲突),这
	里实际视需求配置redis服务,暂时取消主从同步设置。

十六、云服务器部署
只记录关键点

	域名全部换成 mm.jiangjiesheng.cn ,使用内网穿透,打到linux服务器上(从window再转发到linux中,好像就不能使用虚拟域名了,只能使用ip + port 了,所以natapp映射的地址为linux的ip加80端口(nginx默认端口),且注意natapp需要每次启动电脑后更新ip和port。)
	由于natapp只能绑定一个域名,所以使用ip测试,cookiename设置未linux的ip
	直接在linux中执行内网穿透,所以上述配置取消


	首先修改nginx配置,whereis nginx

	添加hosts : vim /etc/hosts
	ping mm.jiangjiesheng.cn测试
	../../sbin/nginx -t 确实配置文件有没有错误
	../../sbin/nginx -s reload
	echo $CATALINA_HOME 看看tomcat在哪里并启动
	分别启动两个tomcat
	wget mm.jiangjiesheng.cn (打到前端首页)
	wget mm.jiangjiesheng.cn/index.jsp (打到接口包,tomcat集群)
	进去tomcat1的logs文件夹
	cd /usr/mylibs/tomcat7/apache-tomcat-7.0.73/logs
	选择一个当前的文件夹
	tail -f  localhost_access_log.2018-03-04.txt
	回车几行 查看后期进入的访问记录
	查看nginx的访问日志
	cd /usr/local/nginx/logs
	tail -f access.log
	打印项目运行日志(同开发时的窗口一样)
	进入tomcat/logs 
	tail -f .catalina.out 查看所有网站运行过程中的日志(同开发过程中的日志)


	Maven配置阿里云软件源
	whereis mvn
	/g-software/maven/apache-maven-3.0.5/conf
	 
	<mirrors> 
	    <mirror> 
	    <id>alimaven</id> 
	    <name>aliyun maven</name> 
	    <url>http://maven.aliyun.com/nexus/content/groups/public/</url> 
	    <mirrorOf>central</mirrorOf>
	    </mirror> 
	</mirrors>
	 
	mvn help:effective-settings
注意:
	特别注意:反复执行tomcat启动后,方向效果和预想的不同(启动很多的tomcat)
	ps -ef | grep tomcat 
	kill -p PID

	netstat -ntlp 
	如果catalina 的  127.0.0.1:8005  和  127.0.0.1:9005 没有启动,则不能访问接口
	处理方案 
		1、通过在内部wget 接口强制初始化(不确定是否有效)
	 	2、等候 5分钟左右大概能启动
----------------------
IDEA开发工具快捷键

	打开资源 Ctrl Shift R
	打开类	Ctrl Shift T
	搜索任意文件 Shift Shift

	代码同步 git checkout . 放弃当前已编辑的代码
技术关键词: CentOS(Linux操作系统)、阿里云软件源配置、Tomcat7、Maven项目构建(编译环境隔离)、 vsftp搭建、iptables防火墙配置、MySQL、MyBatis、Nginx负载均衡、tomcat集群、 分布式Redis、Redis分布式锁、SSO单点登录、SpringMVC全局异常、SpringMVC拦截器、 SpringSession、RESTFul API、SpringSchedule定时关单(Cron服务)、 SpringSchedule+Redisson构建分布式任务调度、Git代码管理、nodejs服务、webpack打包、 Lombok插件、内网穿透、前后端自动化发布脚本、架构设计(分层架构、模块化、技术选型)、前后端完全分离、 云服务器部署、云监控、多进程调试。 业务关键词: 用户模块、后台管理模块、分类管理模块、商品管理、购物车模块、收货地址管理、订单模块、 支付模块(支付宝扫码支付)。
三个阶段总目录 //tech.jiangjiesheng.cn/dev/study/java/mmall 查看端口 netstat -ntlp catalina stop 在ps进程中也看不到 echo "找个比较安全的路径执行 wget 127.0.0.1:8080/index.jsp 等待强制初始化"