java程序员5年后的技术思考(2018)
5年前,做的项目只在单机上运行,数据量不大,3大框架+一个数据库是标配
5年后,项目集群部署,数据量大,编程中要考虑分布式锁,分库,缓存等。考虑项目时,需要加入新的元素,势必也会引入新的问题
现在开发中,哪些想法是标配呢?
#分布式锁
锁存在的意义是控制对共享资源的访问,共享资源主要有类变量、数据库、硬件资源等。现在编程中,类变量已经用的很少了
单机运行时,javaapi可以实现锁(允许同一时间只有一个方法在执行)
集群部署javaapi就无能为力,毕竟javaapi在不同机器或jvm上运行,两者没有联系
至于分布式锁的场景,在编写的带有关键词synchronized的代码,严格意义上,基本都需要由分布式锁替代.在一般的大企业的应用中,基本都是集群部署。
基于分布式锁有3种实现方式:
1.基于数据库的锁机制
1.1通过插入一条满足唯一性的记录实现分布式锁
1.2通过数据库中预设的记录实现排他锁(forupdate-行级锁,前提是过滤的字段有唯一性索引,要不然就不能叫行级锁了)
springquartz就是通过这种方法实现的
这个是数据库悲观锁,其实还有一种乐观锁。比较典型的例子是svn提交,都可以提交,失败就回滚
2.基于缓存redis的锁机制
做的业务场景比较简单,所以很少用到redis锁,实在想不出分布式锁主要用在哪些地方。只能说贫(业)穷(务)限制了我对锁的想象力
锁->资源->数据库:什么数据库资源只能同时一个人访问,可能在秒杀活动能遇到(同一时间,万级别的人抢几件商品)
加锁:
public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } }
解锁:
public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
引用:
Redis分布式锁的正确实现方式(Java版)
http://www.importnew.com/27477.html
3.基于zookeeper的锁机制
数据库锁:sql
redis锁:jedisapi+redis服务器(原理同数据库分布式锁)
zookeeper锁:curatorapi+zookeeper服务器
#缓存
对于一些简单的应用或场景或业务,直接查询数据库就能解决问题。再复杂一点也是用到第三方缓存组件
对于一些比较复杂的场景或业务,就要借助缓存服务器解决响应慢的问题
现在比较火的就是redis
在一般的企业级应用中,也就是用hashmap或list这样的类变量做为简单的缓存。就算是负载均衡部署,几个应用都从数据库中加载数据,只要数据不变也不会有多大问题。但是对于大型分布式应用,对数据的一致性要求比较高,这时候第三方缓存服务器就有用武之地了。
对于系统中,通过类变量hashmap或list来存储缓存数据的.用户量增加,分布式部署时,都可以通过redis替换
redis可用于解决tomcat启动过程中的session丢失问题
以获取最近25条评论为例
@Test public void test0() { String key = "latest.comments"; // 准备初始化数据 ListOperations<Serializable, Object> listOp = redisTemplate.opsForList(); for (int i = 1; i <= 100; i++) { listOp.leftPush(key, i); } // 最近的25条记录 listOp.trim(key, 0, 25); // 查询最近10条记录 int startPage = 3; int nums = 10; List<Object> list1 = listOp.range(key, (startPage - 1)*nums, startPage*nums -1); if (null != list1 && list1.size() == nums) { // 全部在缓存中 for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); } } else { System.out.println("查询数据库获取历史记录"); } }
#对外接口设计
对于一个有价(liu)值(liang)的业务,不仅要能提供直接浏览器访问,更需要提供可供其他系统调用的接口。怎么设计这些接口呢
2个层面考虑:
1.技术选型
http,hessian,netty,mq
2.流量控制
#配置文件
项目集群或分布式部署,配置文件中参数太多,重新部署太耗费时间或直接在运行项目中修改,地方太多。
更明确的方法是有一个配置管理系统
在spring项目中,通过覆盖属性配置类,启动应用时,调用配置系统设置参数。
#单点登录
#分库分表机制
可以从2个方面考虑:
1.DB集群配置
2.代码实现
spring环境中,多数据源的核心类是AbstractRoutingDataSource.
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DbRouterLocalContext.getDbKey(); } }
获取当前环境数据源
public class DbRouterLocalContext { private static final ThreadLocal<String> dbKeyLocal = new ThreadLocal<String>(); private static final ThreadLocal<String> tblKeyLocal = new ThreadLocal<String>(); public static void setDbKey (String dbIdx) { dbKeyLocal.set(dbIdx); } public static String getDbKey() { return dbKeyLocal.get(); } public static void setTblKey (String tblIdx) { tblKeyLocal.set(tblIdx); } public static String getTblKey() { return tblKeyLocal.get(); } }
当前环境使用数据库的配置,一般在dao层设置.根据传入的id或其他值设置规则,应该传入哪个数据库。此时将涉及springaop的编写
基本思路:
定义切面aspect,定义连接点pointcut(结合注解)
xml中配置:
数据库操作模板中,设置dyDataSource
<bean id="dyDataSource" class="com.byron.sharding.router.DynamicRoutingDataSource"> <property name="targetDataSources"> <map> <entry key="db1" value-ref="dataSource01" /> <entry key="db2" value-ref="dataSource02" /> <entry key="db3" value-ref="dataSource03" /> </map> </property> <property name="defaultTargetDataSource" ref="dataSource01" /> </bean>
参考:
http://flysoloing.github.io/2015/09/20/spring-database-sharding-practice/
#文件服务器
不要脱离业务学习技术,学习新技术同时学习新业务场景,即使这些场景你用不到