事务消息
事务消息是保证分布式系统中,双方同时执行成功,或有一方失败即回滚的解决方案?
- 事务消息流程
- 发送半消息(这一步也是对消息服务正常的很好的证明)
- 成功后,执行本地事务
- 上一步成功后发送半消息确认消息
- 不使用事务消息行吗?按照以下流程
// 开始事务
try {
// 1.执行数据库操作
// 2.提交事务
}catch (Exception e){
// 3.回滚事务
}
// 4.发送 mq 消息
这样做你无法保证消息是否能发送成功,会产生不同步的结果
java信号量Semphore和锁的区别
- 锁是互斥量,保证了资源的同步
- semphore可以允许同时执行多个线程,并不限制多线程对资源的访问,如果信号量最大并发线程数为1,也可以实现锁的效果
高并发下锁优化手段
- 减小所得粒度
- 减小锁占用的时间,尽早释放公共资源
- 读写分离代替独占锁
- 锁分离(读写锁的进一步延伸)
- 典型案例就是 LinkedBlockingQueue 的实现,take() 和 put(),操作持有不同的锁
- 锁粗化
- 对于频繁加锁解锁的情况,所在循环的外层
synchronized(lock){ for(int i=0; i<CIRCLE; i++){ //do something } }
API鉴权
礼券码生成策略
url短地址生成策略
- 自增序列算法 也叫永不重复算法
设置 id 自增,一个 10进制 id 对应一个 62进制的数值,1对1,也就不会出现重复的情况。
这个利用的就是低进制转化为高进制时,字符数会减少的特性。
短址的长度一般设为 6 位,而每一位是由 [a - z, A - Z, 0 - 9] 总共 62 个字母组成的,
所以 6 位的话,总共会有 62^6 ~= 568亿种组合,基本上够用了。
- 算法二
将长网址 md5 生成 32 位签名串,分为 4 段, 每段 8 个字节
对这四段循环处理, 取 8 个字节, 将他看成 16 进制串与 0x3fffffff(30位1) 与操作, 即超过 30 位的忽略处理
这 30 位分成 6 段, 每 5 位的数字作为字母表的索引取得特定字符, 依次进行获得 6 位字符串
总的 md5 串可以获得 4 个 6 位串,取里面的任意一个就可作为这个长 url 的短 url 地址
这种算法,虽然会生成4个,但是仍然存在重复几率
- 两种算法对比
第一种算法的好处就是简单好理解,永不重复。但是短码的长度不固定,
随着 id 变大从一位长度开始递增。如果非要让短码长度固定也可以就是让 id 从指定的数字开始递增就可以了。
百度短网址用的这种算法。上文说的开源短网址项目 YOURLS 也是采用了这种算法。源码学习
第二种算法,存在碰撞(重复)的可能性,虽然几率很小。短码位数是比较固定的。
不会从一位长度递增到多位的。据说微博使用的这种算法。
我使用的算法一。有一个不太好的地方就是出现的短码是有序的,可能会不安全。
我的处理方式是构造 62进制的字母不要按顺序排列。
不使用分布式锁 解决并发问题
- 乐观锁
/**
* 该方法返回是否秒杀成功
* @param productId 产品ID
* @return 是否成功
*/
private boolean isSuccess(int productId) {
int affectedCount = 0;
while (affectedCount == 0) {
ProductStock product = excute("select * from product_stock where product_id=#{productId}");
if (product.getNumber>0) {
affectedCount = excute("update product_stock set number=number-1 where product_id=#{productId} and number=#{product.getNumber}");
} else {
return false;
}
}
return true;
/*
更简洁的写法
int affectedCount =
excute("update product_stock set number=number-1 where product_id=#{productId} and number>0");
return affectedCount!=0;*/
}
- 悲观锁
/**
* 该方法返回是否秒杀成功
* @param productId 产品ID、
* @return 是否秒杀成功
*/
private boolean isSuccess(int productId) {
ProductStock product = excute("select * from product_stock where product_id=#{productId} for update");
if (product.getNumber>0) {
excute("update product_stock set number=number-1 where product_id=#{productId}");
return true;
} else {
return false;
}
}
乐观锁与悲观锁的区别
乐观锁的思路一般是表中增加版本字段,更新时where语句中增加版本的判断,算是一种CAS(Compare And Swep)操作,商品库存场景中number起到了版本控制的作用(and number=#{product.getNumber})。悲观锁之所以是悲观,在于他认为外面的世界太复杂,所以一开始就对商品加上锁(select ... for update),后面可以安心的做判断和更新,因为这时候不会有别人更新这条商品库存。
缓存更新策略的优缺点
- 先写库,在删除缓存(业界常用)
- 不过这种情况出现的可能很小,他需要 读操作早于写操作进入数据库,晚于写操作更新缓存
- 先更新数据库,再更新缓存
- 出错几率相对较高
- 先删缓存,再更新数据库
- 这个在并发下有明显问题
mysql mavcc 是怎么实现的?
- 极客时间mysql专栏
AQS
-
java 并发包
- 双端队列使用场景
- 当执行某个工作时可能导致出现更多的工作。比如 广度优先搜索算法(层序遍历二叉树),在遍历过程中不断的需要放入和读出;比如 爬虫抓页面 垃圾回收标记过程
- ConcurrentHashMap
- 初始大小 16
- 负载因子 0.75
- 链表长度大于8转为红黑树
- 红黑树节点小于6转回链表
- 以上特性和hashmap保持一致
- ConcurrentNavigableMap
- It is an extension of SortedMap which provides convenient navigation methods like lowerKey, floorKey, ceilingKey and higherKey, and along with this popular navigation method.
- 是一个支持并发访问的 java.util.NavigableMap,它还能让它的子 map 具备并发访问的能力。所谓的 “子 map” 指的是诸如 headMap(),subMap(),tailMap() 之类的方法返回的 map
- 例如treeMap
- CountDownLatch
- 它允许一个或多个线程等待countDownLatch指定数量的操作完成
- CyclicBarrier
- 一种同步机制,它能够对处理一些算法的线程实现同步。换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情
- 满足以下任何条件都可以让等待 CyclicBarrier 的线程释放:
- 最后一个线程也到达 CyclicBarrier(调用 await())
- 当前线程被其他线程打断(其他线程调用了这个线程的 interrupt() 方法)
- 其他等待栅栏的线程被打断
- 其他等待栅栏的线程因超时而被释放
- 外部线程调用了栅栏的 CyclicBarrier.reset() 方法
- 他可以重用,这是它和CountDownLatch的区别
- Semaphore
- 信号量,Semaphore可以控同时访问的线程个数
- 他和锁的区别是:java信号量Semphore和锁的区别
- 通过这个参数以告知 Semaphore 是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否则不要启用它。
以下是如何在公平模式创建一个 Semaphore 的示例:
Semaphore semaphore = new Semaphore(1, true);
- Exchanger
- 类表示一种两个线程可以进行互相交换对象的会和点
- ExecutorService
- 线程池实现
ExecutorService es = Executors.newFixedThreadPool(4);- 有几种不同的方式来将任务委托给 ExecutorService 去执行:
- execute(Runnable)
- 没有办法得知被执行的 Runnable 的执行结果
- submit(Runnable)
- 它返回一个 Future 对象。这个 Future 对象可以用来检查 Runnable 是否已经执行完毕;
- submit(Callable) 他可以得到执行结果,其他submit(Runnable)一样
- invokeAny()
- invokeAny() 方法要求一系列的 Callable 或者其子接口的实例对象。调用这个方法并不会返回一个 Future,但它返回其中一个 Callable 对象的结果。无法保证返回的是哪个 Callable 的结果 - 只能表明其中一个已执行结束。如果其中一个任务执行结束(或者抛了一个异常),其他 Callable 将被取消。
- invokeAll()
- invokeAll() 方法将调用你在集合中传给 ExecutorService 的所有 Callable 对象。invokeAll() 返回一系列的 Future 对象,通过它们你可以获取每个 Callable 的执行结果。记住,一个任务可能会由于一个异常而结束,因此它可能没有 “成功”。无法通过一个 Future 对象来告知我们是两种结束中的哪一种。
- execute(Runnable)
- ThreadPoolExecutor
- ExecutorService的一个实现
- ScheduledExecutorService
- 是一个 ExecutorService, 它能够将任务延后执行,或者间隔固定时间多次执行。 任务由一个工作者线程异步执行,而不是由提交任务给 ScheduledExecutorService 的那个线程执行。
- ForkJoinPool
- 任务分割、合并框架
- RecursiveAction 无返回结果
- RecursiveTask 有返回结果
- Lock
- lock() 优先考虑获取锁,待获取锁成功后,才响应中断。
- lockInterruptibly() 优先考虑响应中断,而不是响应锁的普通获取或重入获取。
- 原子操作
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
- 提供了一个可以被原子性读和写的对象引用变量。原子性的意思是多个想要改变同一个 AtomicReference 的线程不会导致 AtomicReference 处于不一致的状态。