备忘

171 阅读8分钟

事务消息

事务消息是保证分布式系统中,双方同时执行成功,或有一方失败即回滚的解决方案?

  • 事务消息流程
    • 发送半消息(这一步也是对消息服务正常的很好的证明)
    • 成功后,执行本地事务
    • 上一步成功后发送半消息确认消息
  • 不使用事务消息行吗?按照以下流程
// 开始事务
try {
    // 1.执行数据库操作
    // 2.提交事务
}catch (Exception e){
    // 3.回滚事务
}
// 4.发送 mq 消息

这样做你无法保证消息是否能发送成功,会产生不同步的结果

juejin.cn/post/684490…

java信号量Semphore和锁的区别

  • 锁是互斥量,保证了资源的同步
  • semphore可以允许同时执行多个线程,并不限制多线程对资源的访问,如果信号量最大并发线程数为1,也可以实现锁的效果

高并发下锁优化手段

  • 减小所得粒度
  • 减小锁占用的时间,尽早释放公共资源
  • 读写分离代替独占锁
  • 锁分离(读写锁的进一步延伸)
    • 典型案例就是 LinkedBlockingQueue 的实现,take() 和 put(),操作持有不同的锁
  • 锁粗化
    • 对于频繁加锁解锁的情况,所在循环的外层
    synchronized(lock){
        for(int i=0; i<CIRCLE; i++){
            //do something
        }
    }
    

    www.jianshu.com/p/ff6e46a1d…

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),后面可以安心的做判断和更新,因为这时候不会有别人更新这条商品库存。

缓存更新策略的优缺点

  • 先写库,在删除缓存(业界常用)
    • 不过这种情况出现的可能很小,他需要 读操作早于写操作进入数据库,晚于写操作更新缓存

image.png

  • 先更新数据库,再更新缓存
    • 出错几率相对较高

image.png

  • 先删缓存,再更新数据库
    • 这个在并发下有明显问题

image.png

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的区别

image.png

  • 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 对象来告知我们是两种结束中的哪一种。
  • ThreadPoolExecutor
    • ExecutorService的一个实现
  • ScheduledExecutorService
    • 是一个 ExecutorService, 它能够将任务延后执行,或者间隔固定时间多次执行。 任务由一个工作者线程异步执行,而不是由提交任务给 ScheduledExecutorService 的那个线程执行。
  • ForkJoinPool
    • 任务分割、合并框架
    • RecursiveAction 无返回结果
    • RecursiveTask 有返回结果
  • Lock
    • lock() 优先考虑获取锁,待获取锁成功后,才响应中断。
    • lockInterruptibly() 优先考虑响应中断,而不是响应锁的普通获取或重入获取。
  • 原子操作
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
    • AtomicReference
      • 提供了一个可以被原子性读和写的对象引用变量。原子性的意思是多个想要改变同一个 AtomicReference 的线程不会导致 AtomicReference 处于不一致的状态。